/*******************************************************************************
 * Copyright (c) 2004, 2011 Richard Hoefter and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Richard Hoefter (richard.hoefter@web.de) - initial API and implementation, bug 95297, bug 97051, bug 128103, bug 201180, bug 161354, bug 313386
 *     IBM Corporation - nlsing and incorporating into Eclipse, bug 108276, bug 124210, bug 161845, bug 177833
 *     Nikolay Metchev (N.Metchev@teamphone.com) - bug 108276
 *     Ryan Fong (rfong@trapezenetworks.com) - bug 201143
 *******************************************************************************/

package org.eclipse.ant.internal.ui.datatransfer;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;

import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.ui.AntUIPlugin;
import org.eclipse.ant.internal.ui.model.IAntModelConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.xml.sax.SAXException;

/**
 * Creates build.xml file.
 */
public class BuildFileCreator
{
    protected static final String IMPORT_BUILDFILE_PROCESSING_TARGET = "eclipse.ant.import"; //$NON-NLS-1$
    protected static final String WARNING = " WARNING: Eclipse auto-generated file." + ExportUtil.NEWLINE + //$NON-NLS-1$
                                            "              Any modifications will be overwritten."; //$NON-NLS-1$
    protected static final String NOTE =    "              To include a user specific buildfile here, " + //$NON-NLS-1$
                                            "simply create one in the same" + ExportUtil.NEWLINE + //$NON-NLS-1$
                                            "              directory with the processing instruction " + //$NON-NLS-1$
                                            "<?" +  IMPORT_BUILDFILE_PROCESSING_TARGET + "?>" + ExportUtil.NEWLINE + //$NON-NLS-1$ //$NON-NLS-2$
                                            "              as the first entry and export the buildfile again. "; //$NON-NLS-1$

    protected static String BUILD_XML = "build.xml"; //$NON-NLS-1$
    protected static String JUNIT_OUTPUT_DIR = "junit"; //$NON-NLS-1$
    protected static boolean CHECK_SOURCE_CYCLES = true;
    protected static boolean CREATE_ECLIPSE_COMPILE_TARGET = true;
    
    private Document doc;
    private Element root;
    private IJavaProject project;
    private String projectName;
    private String projectRoot;
    private Map variable2valueMap;
    private Shell shell;
    private Set visited = new TreeSet(); // record used subclasspaths
    private Node classpathNode;
    
    /**
     * Constructor. Please prefer {@link #createBuildFiles(Set, Shell, IProgressMonitor)} if
     * you do not want call the various createXXX() methods yourself.
     * 
     * @param project    create buildfile for this project
     * @param shell      parent instance for dialogs
     */
    public BuildFileCreator(IJavaProject project, Shell shell) throws ParserConfigurationException
    {
        this.project = project;
        this.projectName = project.getProject().getName();
        this.projectRoot = ExportUtil.getProjectRoot(project);
        this.variable2valueMap = new LinkedHashMap();
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        this.doc = dbf.newDocumentBuilder().newDocument();
        this.shell = shell;
    }
      
    /**
     * Create buildfile for given projects.
     * 
     * @param projects    create buildfiles for these <code>IJavaProject</code>
     *                    objects
     * @param shell       parent instance for dialogs
     * @return            project names for which buildfiles were created
     * @throws InterruptedException thrown when user cancels task
     */
    public static List createBuildFiles(Set projects, Shell shell, IProgressMonitor pm)
        throws JavaModelException, ParserConfigurationException,
               TransformerConfigurationException, TransformerException,
               IOException, CoreException, InterruptedException
    {
        List res = new ArrayList();
        try {
            createBuildFilesLoop(projects, shell, pm, res);
        } finally {
            if (pm != null) {
                pm.done();
            }
        }
        return res;
    }

    private static void createBuildFilesLoop(Set projects, Shell shell, IProgressMonitor pm, List res) throws CoreException, ParserConfigurationException,
            JavaModelException, TransformerConfigurationException,
            TransformerFactoryConfigurationError, TransformerException,
            UnsupportedEncodingException {

        // determine files to create/change
        List files = new ArrayList();
        for (Iterator iter = projects.iterator(); iter.hasNext();)
        {
            IJavaProject currentProject = (IJavaProject) iter.next();
            IFile file = currentProject.getProject().getFile(BuildFileCreator.BUILD_XML);
            files.add(file);
        }

        // trigger checkout
        Set confirmedFiles = ExportUtil.validateEdit(shell, files);
        SubMonitor localmonitor = SubMonitor.convert(pm, DataTransferMessages.AntBuildfileExportPage_0, confirmedFiles.size());
        try {
			Iterator iter= projects.iterator();
			while (iter.hasNext())
            {
                IJavaProject currentProject = (IJavaProject) iter.next();
                IFile file = currentProject.getProject().getFile(BuildFileCreator.BUILD_XML);
                if (! confirmedFiles.contains(file))
                {
                    continue;
                }
                
                localmonitor.setTaskName(NLS.bind(DataTransferMessages.BuildFileCreator_generating_buildfile_for, currentProject.getProject().getName()));
                
                BuildFileCreator instance = new BuildFileCreator(currentProject, shell);
                instance.createRoot();
                instance.createImports();
                EclipseClasspath classpath = new EclipseClasspath(currentProject);
                if (CHECK_SOURCE_CYCLES) {
                    SourceAnalyzer.checkCycles(currentProject, classpath, shell);
                }
                instance.createClasspaths(classpath);
                instance.createInit(classpath.srcDirs, classpath.classDirs,
                    classpath.inclusionLists, classpath.exclusionLists);   
                instance.createClean(classpath.classDirs);
                instance.createCleanAll();
                instance.createBuild(classpath.srcDirs, classpath.classDirs,
                                     classpath.inclusionLists, classpath.exclusionLists);
                instance.createBuildRef();
                if (CREATE_ECLIPSE_COMPILE_TARGET) {
                    instance.addInitEclipseCompiler();
                    instance.addBuildEclipse();
                }
                instance.createRun();           
                instance.addSubProperties(currentProject, classpath);
                instance.createProperty();
    
                // write build file
                String xml = ExportUtil.toString(instance.doc);
                InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$
                if (file.exists())
                {
                    file.setContents(is, true, true, null);
                }
                else
                {
                    file.create(is, true, null);
                }    
                if(localmonitor.isCanceled()) {
                    return;
                }
                localmonitor.worked(1);
                res.add(instance.projectName);
            }
        }
        finally {
            if(!localmonitor.isCanceled()) {
                localmonitor.done();
            }
        }
    }

    /**
     * Add property tag.
     */
    public void createProperty()
    {
        // read debug options from Eclipse settings
        boolean source = JavaCore.GENERATE.equals(project.getOption(JavaCore.COMPILER_SOURCE_FILE_ATTR, true));
        boolean lines = JavaCore.GENERATE.equals(project.getOption(JavaCore.COMPILER_LINE_NUMBER_ATTR, true));
        boolean vars = JavaCore.GENERATE.equals(project.getOption(JavaCore.COMPILER_LOCAL_VARIABLE_ATTR, true));
        
        List debuglevel = new ArrayList();
        if (source)
        {
            debuglevel.add("source"); //$NON-NLS-1$
        }
        if (lines)
        {
            debuglevel.add("lines"); //$NON-NLS-1$
        }
        if (vars)
        {
            debuglevel.add("vars"); //$NON-NLS-1$
        }
        if (debuglevel.size() == 0)
        {
            debuglevel.add("none"); //$NON-NLS-1$
        }
        variable2valueMap.put("debuglevel", ExportUtil.toString(debuglevel, ",")); //$NON-NLS-1$ //$NON-NLS-2$

        // "Generated .class files compatibility"
        String target = project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); 
        variable2valueMap.put("target", target); //$NON-NLS-1$

        // "Compiler compliance level"
        //String compliance = project.getOption(JavaCore.COMPILER_COMPLIANCE, true); 
        
        // "Source compatibility"
        String sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true);
        variable2valueMap.put("source", sourceLevel); //$NON-NLS-1$
        
        // <property name="x" value="y"/>
        boolean first = true;
        Node node = root.getFirstChild();
        for (Iterator iterator = variable2valueMap.keySet().iterator(); iterator.hasNext();)
        {
            String key = (String) iterator.next();
            String value = (String) variable2valueMap.get(key);
            Element prop = doc.createElement("property"); //$NON-NLS-1$
            prop.setAttribute(IAntCoreConstants.NAME, key);
            prop.setAttribute(IAntCoreConstants.VALUE, value);
            if (first)
            {
                first = false;               
            }
            else
            {
                node = node.getNextSibling();
            }
            node = root.insertBefore(prop, node);
        }
        
        // <property environment="env"/>
        Element env = doc.createElement("property"); //$NON-NLS-1$
        env.setAttribute("environment", "env"); //$NON-NLS-1$ //$NON-NLS-2$
        root.insertBefore(env, root.getFirstChild());
    }

    /**
     * Create project tag.
     */
    public void createRoot()
    {   
        // <project name="hello" default="build" basedir=".">
        root = doc.createElement("project"); //$NON-NLS-1$
        root.setAttribute(IAntCoreConstants.NAME , projectName);
        root.setAttribute(IAntCoreConstants.DEFAULT , "build"); //$NON-NLS-1$
        root.setAttribute("basedir" , "."); //$NON-NLS-1$ //$NON-NLS-2$
        doc.appendChild(root);
        
        // <!-- warning -->
        Comment comment = doc.createComment(WARNING + ExportUtil.NEWLINE + NOTE);
        doc.insertBefore(comment, root);
    }
    
    /**
     * Find buildfiles in projectroot directory and automatically import them.
     */
    public void createImports()
    {
        // <import file="javadoc.xml"/>
        File dir = new File(projectRoot);
        FilenameFilter filter = new FilenameFilter()
        {
            public boolean accept(File acceptDir, String name)
            {
                return name.endsWith(".xml"); //$NON-NLS-1$
            }
        };

        File[] files = dir.listFiles(filter);
        if (files == null)
        {
            return;
        }
        for (int i = 0; i < files.length; i++)
        {
            // import file if it is an XML document with marker comment as first
            // child
            File file = files[i];
            Document docCandidate;
            try {
                docCandidate = ExportUtil.parseXmlFile(file);
                NodeList nodes = docCandidate.getChildNodes();
                for (int j = 0; j < nodes.getLength(); j++) {
                    Node node = nodes.item(j);
                    if (node instanceof ProcessingInstruction &&  
                            IMPORT_BUILDFILE_PROCESSING_TARGET.equals(((ProcessingInstruction) node).getTarget().trim())) {
                        Element element = doc.createElement("import"); //$NON-NLS-1$
                        element.setAttribute(IAntCoreConstants.FILE, file.getName());
                        root.appendChild(element);
                        break;
                    }
                }
            } catch (ParserConfigurationException e){
                AntUIPlugin.log("invalid XML file not imported: " + file.getAbsolutePath(), e); //$NON-NLS-1$
            } catch (SAXException e) {
                AntUIPlugin.log("invalid XML file not imported: " + file.getAbsolutePath(), e); //$NON-NLS-1$
            } catch (IOException e) {
                AntUIPlugin.log("invalid XML file not imported: " + file.getAbsolutePath(), e); //$NON-NLS-1$
            }
        }
    }

    /**
     * Create classpath tags.
     */
    public void createClasspaths(EclipseClasspath classpath) throws JavaModelException
    {
        createClasspaths(null, project, classpath);
    }
    
    /**
     * Create classpath tags. Allows to specify ID.
     * @param pathId    specify id, if null project name is used
     */
    public void createClasspaths(String pathId, IJavaProject currentProject, EclipseClasspath classpath)
        throws JavaModelException
    {
        if (currentProject == null)
        {
            AntUIPlugin.log("project is not loaded in workspace: " + pathId, null); //$NON-NLS-1$ 
            return;
        }
        // <path id="hello.classpath">
        //     <pathelement location="${hello.location}/classes"/>
        //     <pathelement location="${hello.location}/x.jar"/>
        //     <path refid="${goodbye.classpath}"/>
        // </path>
        Element element = doc.createElement("path"); //$NON-NLS-1$
        String pathid = pathId;
        if (pathid == null)
        {
            pathid = currentProject.getProject().getName() + ".classpath"; //$NON-NLS-1$
        }
        element.setAttribute("id", pathid); //$NON-NLS-1$
        visited.add(pathid);
        variable2valueMap.putAll(classpath.variable2valueMap);
        for (Iterator iter = ExportUtil.removeDuplicates(classpath.rawClassPathEntries).iterator(); iter.hasNext();)
        {
            String entry = (String) iter.next(); 
            if (EclipseClasspath.isProjectReference(entry))
            {
                Element pathElement = doc.createElement("path"); //$NON-NLS-1$
                IJavaProject referencedProject = EclipseClasspath.resolveProjectReference(entry); 
                if (referencedProject == null)
                {
                    AntUIPlugin.log("project is not loaded in workspace: " + pathid, null); //$NON-NLS-1$
                    continue;
                }
                String refPathId = referencedProject.getProject().getName() + ".classpath"; //$NON-NLS-1$
                pathElement.setAttribute("refid", refPathId); //$NON-NLS-1$
                element.appendChild(pathElement);               
                if (visited.add(refPathId))
                {
                    createClasspaths(null, referencedProject, new EclipseClasspath(referencedProject)); // recursion
                }
            }
            else if (EclipseClasspath.isUserLibraryReference(entry) ||
                     EclipseClasspath.isLibraryReference(entry))
            {
                addUserLibrary(element, entry);
            }
            else if (EclipseClasspath.isUserSystemLibraryReference(entry))
            {
                if (pathid.endsWith(".bootclasspath")) { //$NON-NLS-1$
                    addUserLibrary(element, entry);
                }
            }            
            else if (EclipseClasspath.isJreReference(entry))
            {
                if (pathid.endsWith(".bootclasspath")) { //$NON-NLS-1$
                    addJre(element);
                }
            }
            else
            {
                // prefix with ${project.location}
                String prefix = IAntCoreConstants.EMPTY_STRING;
                if (!entry.startsWith("${") &&                                  // no variable ${var}/classes //$NON-NLS-1$
                    !projectName.equals(currentProject.getProject().getName())) // not main project 
                {
                    String currentProjectRoot= ExportUtil.getProjectRoot(currentProject);
                    entry= ExportUtil.getRelativePath(entry, currentProjectRoot);
                    if (!new Path(entry).isAbsolute()) {
                        prefix = "${" + currentProject.getProject().getName() + ".location}/"; //$NON-NLS-1$ //$NON-NLS-2$
                    }
                }
                Element pathElement = doc.createElement("pathelement"); //$NON-NLS-1$
                String path = ExportUtil.getRelativePath(prefix + entry, projectRoot);
                pathElement.setAttribute(IAntModelConstants.ATTR_LOCATION, path);
                element.appendChild(pathElement);
            }
        }
        addToClasspathBlock(element);
    }

    private void addUserLibrary(Element element, String entry)
    {
        // add classpath reference
        Element pathElement = doc.createElement("path"); //$NON-NLS-1$
        IClasspathContainer container = EclipseClasspath.resolveUserLibraryReference(entry); 
        String name = ExportUtil.removePrefixAndSuffix(entry, "${", "}"); //$NON-NLS-1$ //$NON-NLS-2$
        pathElement.setAttribute("refid", name); //$NON-NLS-1$
        element.appendChild(pathElement);

        // add classpath
        if (visited.add(entry))
        {
            Element userElement = doc.createElement("path"); //$NON-NLS-1$
            userElement.setAttribute("id", name); //$NON-NLS-1$
            IClasspathEntry entries[] = container.getClasspathEntries();
            for (int i = 0; i < entries.length; i++)
            {   
                String jarFile = entries[i].getPath().toString();
                // use ECLIPSE_HOME variable for library jars
                if (EclipseClasspath.isLibraryReference(entry))
                {
                    IPath home = JavaCore.getClasspathVariable("ECLIPSE_HOME"); //$NON-NLS-1$
                    if (home != null && home.isPrefixOf(entries[i].getPath()))
                    {
                        variable2valueMap.put("ECLIPSE_HOME", home.toString()); //$NON-NLS-1$
                        jarFile = "${ECLIPSE_HOME}" + jarFile.substring(home.toString().length()); //$NON-NLS-1$ 
                    }
                    else if (! new File(jarFile).exists() &&
                            jarFile.startsWith('/' + projectName) &&
                            new File(projectRoot, jarFile.substring(('/' + projectName).length())).exists())
                    {
                        // workaround that additional jars are stored with
                        // leading project root in container object, although
                        // they are relative and indeed correctly stored in
                        // build.properties (jars.extra.classpath)
                        jarFile = jarFile.substring(('/' + projectName).length() + 1);
                    }
                }
                jarFile = ExportUtil.getRelativePath(jarFile, projectRoot);
                Element userPathElement = doc.createElement("pathelement"); //$NON-NLS-1$
                userPathElement.setAttribute(IAntModelConstants.ATTR_LOCATION, jarFile);
                userElement.appendChild(userPathElement);
            }
            addToClasspathBlock(userElement);
        }
    }
    
    /**
     * Add JRE to given classpath.
     * @param element   classpath tag
     */
    private void addJre(Element element)
    {
        // <fileset dir="${java.home}/lib" includes="*.jar"/>
        // <fileset dir="${java.home}/lib/ext" includes="*.jar"/>
        Element pathElement = doc.createElement("fileset"); //$NON-NLS-1$
        pathElement.setAttribute(IAntCoreConstants.DIR, "${java.home}/lib"); //$NON-NLS-1$
        pathElement.setAttribute("includes", "*.jar"); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(pathElement);
        pathElement = doc.createElement("fileset"); //$NON-NLS-1$
        pathElement.setAttribute(IAntCoreConstants.DIR, "${java.home}/lib/ext"); //$NON-NLS-1$
        pathElement.setAttribute("includes", "*.jar"); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(pathElement);
    }

    private void addToClasspathBlock(Element element)
    {
        // remember node to insert all classpaths at same location
        if (classpathNode == null)
        {
            classpathNode = root.appendChild(element);
        }
        else
        {
            classpathNode = classpathNode.getNextSibling();
            classpathNode = root.insertBefore(element, classpathNode);
        }
    }
    
    /**
     * Add properties of subprojects to internal properties map.
     */
    public void addSubProperties(IJavaProject subproject, EclipseClasspath classpath) throws JavaModelException
    { 
        for (Iterator iterator = ExportUtil.getClasspathProjectsRecursive(subproject).iterator(); iterator.hasNext();)
        {
            IJavaProject subProject = (IJavaProject) iterator.next(); 
            String location = subProject.getProject().getName() + ".location"; //$NON-NLS-1$
            // add subproject properties to variable2valueMap
            String subProjectRoot = ExportUtil.getProjectRoot(subProject);
            String relativePath = ExportUtil.getRelativePath(subProjectRoot,
                    projectRoot);
            variable2valueMap.put(location, relativePath);
            variable2valueMap.putAll(classpath.variable2valueMap);    
        }
    }

    /**
     * Create init target. Creates directories and copies resources.
     * @param srcDirs            source directories to copy resources from
     * @param classDirs          classes directories to copy resources to
     */
    public void createInit(List srcDirs, List classDirs,
        List inclusionLists, List exclusionLists)
    {
        // <target name="init">
        //     <mkdir dir="classes"/>
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "init"); //$NON-NLS-1$
        List classDirsUnique = ExportUtil.removeDuplicates(classDirs);        
        for (Iterator iterator = classDirsUnique.iterator(); iterator.hasNext();)
        {            
            String classDir = (String) iterator.next();
            if (!classDir.equals(".") && //$NON-NLS-1$
                !EclipseClasspath.isReference(classDir))
            {
                Element pathElement = doc.createElement("mkdir"); //$NON-NLS-1$
                pathElement.setAttribute(IAntCoreConstants.DIR, classDir);
                element.appendChild(pathElement);
            }
        }
        root.appendChild(element);
        
        createCopyResources(srcDirs, classDirs, element, inclusionLists,
            exclusionLists);
    }

    private void createCopyResources(List srcDirs, List classDirs, Element element,
        List inclusionLists, List exclusionLists)
    {
        // Check filter for copying resources
        String filter = project.getOption(JavaCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER, true);
        StringTokenizer tokenizer = new StringTokenizer(filter, ","); //$NON-NLS-1$
        List filters = Collections.list(tokenizer);
        filters.add("*.java"); //$NON-NLS-1$
        
        // prefix filters with wildcard
        for (int i = 0; i < filters.size(); i++)
        {
            String item = ((String) filters.get(i)).trim();
            if (item.equals("*")) //$NON-NLS-1$
            {
                // everything is excluded from copying
                return;
            }            
            filters.set(i, "**/" + item); //$NON-NLS-1$
        }
        
        // <copy todir="classes" includeemptydirs="false">
        //     <fileset dir="src" excludes="**/*.java"/>
        // </copy>
        for (int i = 0; i < srcDirs.size(); i++)
        {
            String srcDir = (String) srcDirs.get(i);
            String classDir = (String) classDirs.get(i);
            if (! EclipseClasspath.isReference(classDir))
            {
                Element copyElement = doc.createElement("copy"); //$NON-NLS-1$
                copyElement.setAttribute("todir", classDir); //$NON-NLS-1$
                copyElement.setAttribute("includeemptydirs", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                Element filesetElement = doc.createElement("fileset"); //$NON-NLS-1$
                filesetElement.setAttribute(IAntCoreConstants.DIR, srcDir);

                List inclusions = (List) inclusionLists.get(i);
                List exclusions = (List) exclusionLists.get(i);

                for (Iterator iter = inclusions.iterator(); iter.hasNext();)
                {
                    String inclusion = (String) iter.next();
                    Element includeElement = doc.createElement("include"); //$NON-NLS-1$
                    includeElement.setAttribute(IAntCoreConstants.NAME, inclusion);
                    filesetElement.appendChild(includeElement);
                }           
                for (Iterator iter = filters.iterator(); iter.hasNext();)
                {
                    String exclusion = (String) iter.next();
                    Element excludeElement = doc.createElement("exclude"); //$NON-NLS-1$
                    excludeElement.setAttribute(IAntCoreConstants.NAME, exclusion);
                    filesetElement.appendChild(excludeElement);
                }
                for (Iterator iter = exclusions.iterator(); iter.hasNext();)
                {
                    String exclusion = (String) iter.next();
                    Element excludeElement = doc.createElement("exclude"); //$NON-NLS-1$
                    excludeElement.setAttribute(IAntCoreConstants.NAME, exclusion);
                    filesetElement.appendChild(excludeElement);
                }
                
                copyElement.appendChild(filesetElement);
                element.appendChild(copyElement);
            }
        }
    }

    /**
     * Create clean target.
     * @param classDirs    classes directories to delete
     */
    public void createClean(List classDirs)
    {
        // <target name="clean">
        //     <delete dir="classes"/>
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "clean"); //$NON-NLS-1$
        List classDirUnique = ExportUtil.removeDuplicates(classDirs);
        for (Iterator iterator = classDirUnique.iterator(); iterator.hasNext();)
        {
            String classDir = (String) iterator.next();
            if (!classDir.equals(".") && //$NON-NLS-1$
                !EclipseClasspath.isReference(classDir))
            {
                Element deleteElement = doc.createElement("delete"); //$NON-NLS-1$
                deleteElement.setAttribute(IAntCoreConstants.DIR, classDir);
                element.appendChild(deleteElement);
            }
        }
        root.appendChild(element);    

        // <target name="clean">
        //     <delete>
        //         <fileset dir="." includes="**/*.class"/>
        //     </delete>
        // </target>
        if (classDirs.contains(".")) //$NON-NLS-1$
        {
            Element deleteElement = doc.createElement("delete"); //$NON-NLS-1$
            Element filesetElement = doc.createElement("fileset"); //$NON-NLS-1$
            filesetElement.setAttribute(IAntCoreConstants.DIR, "."); //$NON-NLS-1$
            filesetElement.setAttribute("includes", "**/*.class"); //$NON-NLS-1$ //$NON-NLS-2$
            deleteElement.appendChild(filesetElement);           
            element.appendChild(deleteElement);           
        }
    }
    
    /**
     * Create cleanall target.
     */
    public void createCleanAll() throws JavaModelException
    {
        // <target name="cleanall" depends="clean">
        //     <ant antfile="build.xml" dir="${hello.location}" inheritAll="false" target="clean"/>
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "cleanall"); //$NON-NLS-1$
        element.setAttribute("depends", "clean"); //$NON-NLS-1$ //$NON-NLS-2$
        List subProjects = ExportUtil.getClasspathProjectsRecursive(project);
        for (Iterator iterator = subProjects.iterator(); iterator.hasNext();)
        {
            IJavaProject subProject = (IJavaProject) iterator.next();
            Element antElement = doc.createElement("ant"); //$NON-NLS-1$
            antElement.setAttribute("antfile", BUILD_XML); //$NON-NLS-1$
            antElement.setAttribute(IAntCoreConstants.DIR, "${" + subProject.getProject().getName() + ".location}"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("target", "clean");  //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("inheritAll", "false");  //$NON-NLS-1$ //$NON-NLS-2$
            element.appendChild(antElement);
        }
        root.appendChild(element);
    }

    /**
     * Create build target.
     * @param srcDirs           source directories of mainproject
     * @param classDirs         class directories of mainproject
     * @param inclusionLists    inclusion filters of mainproject 
     * @param exclusionLists    exclusion filters of mainproject
     */
    public void createBuild(List srcDirs, List classDirs, List inclusionLists, List exclusionLists) throws JavaModelException
    {
        // <target name="build" depends="build-subprojects,build-project"/>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "build"); //$NON-NLS-1$
        element.setAttribute("depends", "build-subprojects,build-project"); //$NON-NLS-1$ //$NON-NLS-2$
        root.appendChild(element);
        
        // <target name="build-subprojects">
        //     <ant antfile="build.xml" dir="${hello.location}" inheritAll="false" target="build-project"/>
        // </target>
        element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "build-subprojects"); //$NON-NLS-1$
        List subProjects = ExportUtil.getClasspathProjectsRecursive(project);
        for (Iterator iterator = subProjects.iterator(); iterator.hasNext();)
        {
            IJavaProject subProject = (IJavaProject) iterator.next();
            Element antElement = doc.createElement("ant"); //$NON-NLS-1$
            antElement.setAttribute("antfile", BUILD_XML); //$NON-NLS-1$
            antElement.setAttribute(IAntCoreConstants.DIR, "${" + subProject.getProject().getName() + ".location}"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("target", "build-project");  //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("inheritAll", "false");  //$NON-NLS-1$ //$NON-NLS-2$
            if (CREATE_ECLIPSE_COMPILE_TARGET) {
                Element propertysetElement = doc.createElement("propertyset"); //$NON-NLS-1$
                Element propertyrefElement = doc.createElement("propertyref"); //$NON-NLS-1$
                propertyrefElement.setAttribute(IAntCoreConstants.NAME, "build.compiler");  //$NON-NLS-1$
                propertysetElement.appendChild(propertyrefElement);
                antElement.appendChild(propertysetElement);
            }
            element.appendChild(antElement);           
        }
        root.appendChild(element);
        
        // Bug 313386 optimization:
        // Source directories with the same classes directory get only one <javac> tag
        // Side effect: Eclipse inclusion and exclusion filters apply to the specified source directory.
        //              Ant inclusion and exclusion filters apply to all source directories.
        //              This may lead to unexpected behavior.
        HashMap class2sources = new HashMap();
        HashMap class2includes = new HashMap();
        HashMap class2excludes = new HashMap();
        for (int i = 0; i < srcDirs.size(); i++) {
            String srcDir = (String) srcDirs.get(i);
            if (!EclipseClasspath.isReference(srcDir)) {
                String classDir = (String) classDirs.get(i);
                List inclusions = (List) inclusionLists.get(i);
                List exclusions = (List) exclusionLists.get(i);
                List list = (List) class2sources.get(classDir);
                List list2 = (List) class2includes.get(classDir);
                List list3 = (List) class2excludes.get(classDir);
                if (list == null) {
                    list = new ArrayList();
                    list2 = new ArrayList();
                    list3 = new ArrayList();
                    class2sources.put(classDir, list);
                    class2includes.put(classDir, list2);
                    class2excludes.put(classDir, list3);
                }
                list.add(srcDir);
                list2.addAll(inclusions);
                list3.addAll(exclusions);
            }
        }
        
        // <target name="build-project" depends="init">
        //     <echo message="${ant.project.name}: ${ant.file}"/>
        //     <javac destdir="classes">
        //         <src path="src"/>
        //         <include name=""/>
        //         <exclude name=""/>
        //         <classpath refid="project.classpath"/>
        //     </javac>    
        // </target>        
        element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "build-project"); //$NON-NLS-1$
        element.setAttribute("depends", "init"); //$NON-NLS-1$ //$NON-NLS-2$
        Element echoElement = doc.createElement("echo"); //$NON-NLS-1$
        echoElement.setAttribute("message", "${ant.project.name}: ${ant.file}"); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(echoElement);           
        for (int i = 0; i < srcDirs.size(); i++)
        {
            String srcDir = (String) srcDirs.get(i);
            if (!EclipseClasspath.isReference(srcDir))
            {
                String classDir = (String) classDirs.get(i);
                List sources = (List) class2sources.get(classDir);
                List inclusions = (List) class2includes.get(classDir);
                List exclusions = (List) class2excludes.get(classDir);
                if (sources != null && sources.size() > 1) {
                    // remove list to exclude it from the next iteration
                    class2sources.put(classDir, null);
                }
                else if (sources == null) {
                    continue;
                }
                
                Element javacElement = doc.createElement("javac"); //$NON-NLS-1$
                javacElement.setAttribute("includeantruntime", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                javacElement.setAttribute("destdir", classDir); //$NON-NLS-1$
                javacElement.setAttribute("debug", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                javacElement.setAttribute("debuglevel", "${debuglevel}"); //$NON-NLS-1$ //$NON-NLS-2$
                javacElement.setAttribute("source", "${source}"); //$NON-NLS-1$ //$NON-NLS-2$                
                javacElement.setAttribute("target", "${target}"); //$NON-NLS-1$ //$NON-NLS-2$
                
                // Bug 313386: <javac> tag with several source directories
                //assert list.size() != 1 || srcDir.equals(list.get(i));
                for (Iterator iterator = sources.iterator(); iterator.hasNext();) {
                    String s = (String) iterator.next();
                    Element srcElement = doc.createElement("src"); //$NON-NLS-1$
                    srcElement.setAttribute("path", s); //$NON-NLS-1$
                    javacElement.appendChild(srcElement);
                }

                for (Iterator iter = inclusions.iterator(); iter.hasNext();)
                {
                    String inclusion = (String) iter.next();
                    Element includeElement = doc.createElement("include"); //$NON-NLS-1$
                    includeElement.setAttribute(IAntCoreConstants.NAME, inclusion);
                    javacElement.appendChild(includeElement);
                }      
                
                for (Iterator iter = exclusions.iterator(); iter.hasNext();)
                {
                    String exclusion = (String) iter.next();
                    Element excludeElement = doc.createElement("exclude"); //$NON-NLS-1$
                    excludeElement.setAttribute(IAntCoreConstants.NAME, exclusion);
                    javacElement.appendChild(excludeElement);
                }           
                Element classpathRefElement = doc.createElement("classpath"); //$NON-NLS-1$
                classpathRefElement.setAttribute("refid", projectName + ".classpath"); //$NON-NLS-1$ //$NON-NLS-2$
                javacElement.appendChild(classpathRefElement);
                element.appendChild(javacElement);
                
                addCompilerBootClasspath(srcDirs, javacElement);
            }
        }
        root.appendChild(element);
    }
    
    /**
     * Create target build-refprojects which compiles projects which reference
     * current project.
     */
    private void createBuildRef() throws JavaModelException {
        
        Set refProjects = new TreeSet(ExportUtil.getJavaProjectComparator());
        IJavaProject[] projects = project.getJavaModel().getJavaProjects();
        for (int i = 0; i < projects.length; i++) {
            List subProjects = ExportUtil.getClasspathProjects(projects[i]);
            for (Iterator iter = subProjects.iterator(); iter.hasNext();) {
                IJavaProject p = (IJavaProject) iter.next();
                if (projectName.equals(p.getProject().getName())) {
                    refProjects.add(projects[i]);
                }
            }
        }
        
        // <target name="build-refprojects">
        //     <ant antfile="build.xml" dir="${hello.location}" target="clean" inheritAll="false"/> 
        //     <ant antfile="build.xml" dir="${hello.location}" target="build" inheritAll="false"/> 
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "build-refprojects"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.DESCRIPTION, "Build all projects which " + //$NON-NLS-1$ 
                "reference this project. Useful to propagate changes."); //$NON-NLS-1$
        for (Iterator iter = refProjects.iterator(); iter.hasNext();) {
            IJavaProject p = (IJavaProject) iter.next();
            String location = p.getProject().getName() + ".location"; //$NON-NLS-1$
            String refProjectRoot = ExportUtil.getProjectRoot(p);
            String relativePath = ExportUtil.getRelativePath(refProjectRoot,
                    projectRoot);
            variable2valueMap.put(location, relativePath);

            Element antElement = doc.createElement("ant"); //$NON-NLS-1$
            antElement.setAttribute("antfile", BUILD_XML); //$NON-NLS-1$
            antElement.setAttribute(IAntCoreConstants.DIR, "${" + p.getProject().getName() + ".location}"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("target", "clean"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("inheritAll", "false"); //$NON-NLS-1$ //$NON-NLS-2$
            element.appendChild(antElement);
            
            antElement = doc.createElement("ant"); //$NON-NLS-1$
            antElement.setAttribute("antfile", BUILD_XML); //$NON-NLS-1$
            antElement.setAttribute(IAntCoreConstants.DIR, "${" + p.getProject().getName() + ".location}"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("target", "build"); //$NON-NLS-1$ //$NON-NLS-2$
            antElement.setAttribute("inheritAll", "false");  //$NON-NLS-1$ //$NON-NLS-2$
            if (CREATE_ECLIPSE_COMPILE_TARGET) {
                Element propertysetElement = doc.createElement("propertyset"); //$NON-NLS-1$
                Element propertyrefElement = doc.createElement("propertyref"); //$NON-NLS-1$
                propertyrefElement.setAttribute(IAntCoreConstants.NAME, "build.compiler");  //$NON-NLS-1$
                propertysetElement.appendChild(propertyrefElement);
                antElement.appendChild(propertysetElement);
            }
            element.appendChild(antElement);
        }
        root.appendChild(element);
    }
    
    /**
     * Add target to initialize Eclipse compiler. It copies required jars to ant
     * lib directory.
     */
    public void addInitEclipseCompiler()
    {
        // use ECLIPSE_HOME classpath variable
        IPath value = JavaCore.getClasspathVariable("ECLIPSE_HOME"); //$NON-NLS-1$
        if (value != null) {
            variable2valueMap.put("ECLIPSE_HOME", ExportUtil.getRelativePath( //$NON-NLS-1$
                value.toString(), projectRoot));
        }
        // <target name="init-eclipse-compiler" description="copy Eclipse compiler jars to ant lib directory">
        //     <property name="ECLIPSE_HOME" value="C:/Programme/eclipse-3.1" />
        //     <copy todir="${ant.library.dir}">
        //         <fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar" />
        //     </copy>
        //     <unzip dest="${ant.library.dir}">
        //         <patternset includes="jdtCompilerAdapter.jar" />
        //         <fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar" />
        //     </unzip>
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "init-eclipse-compiler"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.DESCRIPTION, "copy Eclipse compiler jars to ant lib directory"); //$NON-NLS-1$ 
        Element copyElement = doc.createElement("copy"); //$NON-NLS-1$
        copyElement.setAttribute("todir", "${ant.library.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
        Element filesetElement = doc.createElement("fileset"); //$NON-NLS-1$
        filesetElement.setAttribute(IAntCoreConstants.DIR, "${ECLIPSE_HOME}/plugins"); //$NON-NLS-1$
        filesetElement.setAttribute("includes", "org.eclipse.jdt.core_*.jar"); //$NON-NLS-1$ //$NON-NLS-2$
        copyElement.appendChild(filesetElement);
        element.appendChild(copyElement);
        Element unzipElement = doc.createElement("unzip"); //$NON-NLS-1$
        unzipElement.setAttribute("dest", "${ant.library.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
        Element patternsetElement = doc.createElement("patternset"); //$NON-NLS-1$
        patternsetElement.setAttribute("includes", "jdtCompilerAdapter.jar"); //$NON-NLS-1$ //$NON-NLS-2$
        unzipElement.appendChild(patternsetElement);
        unzipElement.appendChild(filesetElement.cloneNode(false));
        element.appendChild(unzipElement);
        root.appendChild(element);
    }

    /**
     * Add target to compile project using Eclipse compiler.
     */
    public void addBuildEclipse()
    {
        // <target name="build-eclipse-compiler" description="compile project with Eclipse compiler">
        //     <property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter" />
        //     <antcall target="build" />
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "build-eclipse-compiler"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.DESCRIPTION, "compile project with Eclipse compiler"); //$NON-NLS-1$ 
        Element propertyElement = doc.createElement("property"); //$NON-NLS-1$
        propertyElement.setAttribute(IAntCoreConstants.NAME, "build.compiler"); //$NON-NLS-1$
        propertyElement.setAttribute(IAntCoreConstants.VALUE, "org.eclipse.jdt.core.JDTCompilerAdapter"); //$NON-NLS-1$
        element.appendChild(propertyElement);
        Element antcallElement = doc.createElement("antcall"); //$NON-NLS-1$
        antcallElement.setAttribute("target", "build"); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(antcallElement);
        root.appendChild(element);
    }

    /**
     * Add all bootclasspaths in srcDirs to given javacElement.
     */
    private void addCompilerBootClasspath(List srcDirs, Element javacElement)
    {
        // <bootclasspath>
        //     <path refid="mylib.bootclasspath"/>
        //     <fileset dir="${java.home}/lib" includes="*.jar"/>
        //     <fileset dir="${java.home}/lib/ext" includes="*.jar"/>
        // </bootclasspath>
        Element bootclasspathElement = doc.createElement("bootclasspath"); //$NON-NLS-1$
        boolean bootclasspathUsed = false;
        for (Iterator iter = srcDirs.iterator(); iter.hasNext();)
        {
            String entry = (String) iter.next();
            if (EclipseClasspath.isUserSystemLibraryReference(entry))
            {
                Element pathElement = doc.createElement("path"); //$NON-NLS-1$                        
                pathElement.setAttribute("refid", ExportUtil.removePrefixAndSuffix(entry, "${", "}")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                bootclasspathElement.appendChild(pathElement);
                bootclasspathUsed = true;
            } else if (EclipseClasspath.isJreReference(entry)) {
                addJre(bootclasspathElement);
            }
        }
        if (bootclasspathUsed)
        {
            javacElement.appendChild(bootclasspathElement);
        }
    }

    /**
     * Add run targets.
     * @throws CoreException thrown if problem accessing the launch configuration
     * @throws TransformerFactoryConfigurationError thrown if applet file could not get created
     * @throws UnsupportedEncodingException thrown if applet file could not get created
     */
    public void createRun() throws CoreException, TransformerFactoryConfigurationError, UnsupportedEncodingException
    {
        // <target name="run">
        //     <java fork="yes" classname="class" failonerror="true" dir="." newenvironment="true">
        //         <env key="a" value="b"/>
        //         <jvmarg value="-Dx=y"/>
        //         <arg value="arg"/>
        //         <classpath refid="project.classpath"/>
        //     </java>
        // </target>
        ILaunchConfiguration[] confs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations();
        boolean junitUsed = false;
        for (int i = 0; i < confs.length; i++)
        {
            ILaunchConfiguration conf = confs[i];
            if (!projectName.equals(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, IAntCoreConstants.EMPTY_STRING)))
            {
                continue;
            }
                    
            if (conf.getType().getIdentifier().equals(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION))
            {
                addJavaApplication(variable2valueMap, conf);
            }
            else if (conf.getType().getIdentifier().equals(IJavaLaunchConfigurationConstants.ID_JAVA_APPLET))
            {
                addApplet(variable2valueMap, conf);
            }
            else if (conf.getType().getIdentifier().equals("org.eclipse.jdt.junit.launchconfig" /*JUnitLaunchConfiguration.ID_JUNIT_APPLICATION*/)) //$NON-NLS-1$
            {                    
                addJUnit(variable2valueMap, conf);
                junitUsed = true;
            }           
        }
        
        if (junitUsed)
        {
            addJUnitReport();
        }
    }

    /**
     * Convert Java application launch configuration to ant target and add it to a document.
     * @param variable2value    adds Eclipse variables to this map,
     *                             if run configuration makes use of this feature
     * @param conf                 Java application launch configuration
     */
    public void addJavaApplication(Map variable2value, ILaunchConfiguration conf) throws CoreException
    {
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, conf.getName());
        Element javaElement = doc.createElement("java"); //$NON-NLS-1$
        javaElement.setAttribute("fork", "yes"); //$NON-NLS-1$ //$NON-NLS-2$
        javaElement.setAttribute("classname", conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, IAntCoreConstants.EMPTY_STRING)); //$NON-NLS-1$
        javaElement.setAttribute("failonerror", "true"); //$NON-NLS-1$ //$NON-NLS-2$
        String dir = conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, IAntCoreConstants.EMPTY_STRING);
        ExportUtil.addVariable(variable2value, dir, projectRoot);                
        if (!dir.equals(IAntCoreConstants.EMPTY_STRING))
        {
            javaElement.setAttribute(IAntCoreConstants.DIR, ExportUtil.getRelativePath(dir, projectRoot));
        }
        if (!conf.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true))
        {
            javaElement.setAttribute("newenvironment", "true"); //$NON-NLS-1$ //$NON-NLS-2$
        }
        Map props = conf.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, new TreeMap());
        addElements(props, doc, javaElement, "env", "key", IAntCoreConstants.VALUE); //$NON-NLS-1$ //$NON-NLS-2$
        addElement(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, IAntCoreConstants.EMPTY_STRING), doc, javaElement, "jvmarg", "line", variable2value, projectRoot); //$NON-NLS-1$ //$NON-NLS-2$
        addElement(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, IAntCoreConstants.EMPTY_STRING), doc, javaElement, "arg", "line", variable2value, projectRoot); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(javaElement);
        
        addRuntimeClasspath(conf, javaElement);
        addRuntimeBootClasspath(conf, javaElement);
        root.appendChild(element);
    }

    /**
     * Convert applet launch configuration to Ant target and add it to a document. 
     * @param variable2value    adds Eclipse variables to this map,
     *                             if run configuration makes use of this feature
     * @param conf                 applet configuration
     * @throws CoreException thrown if problem dealing with launch configuration or underlying resources 
     * @throws TransformerFactoryConfigurationError thrown if applet file could not get created 
     * @throws UnsupportedEncodingException thrown if applet file could not get created
     */
    public void addApplet(Map variable2value, ILaunchConfiguration conf) throws CoreException, TransformerFactoryConfigurationError, UnsupportedEncodingException
    {
        String dir = conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, IAntCoreConstants.EMPTY_STRING);
        if (dir.equals(IAntCoreConstants.EMPTY_STRING))
        {
            dir = projectRoot;
        }
        ExportUtil.addVariable(variable2value, dir, projectRoot);
        String value;
        try
        {
            value = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(dir);    
        }
        catch (CoreException e)
        {
            // cannot resolve variable
            value = null;
        }

        String htmlfile = ((value != null) ? value : dir) + '/' + conf.getName() + ".html"; //$NON-NLS-1$
        // confirm overwrite
        if (ExportUtil.existsUserFile(htmlfile) && !MessageDialog.openConfirm(shell, DataTransferMessages.AntBuildfileExportPage_4, DataTransferMessages.AntBuildfileExportPage_4 + ": " + htmlfile)) //$NON-NLS-1$
        {
            return;
        }
        IJavaProject javaProject = ExportUtil.getJavaProjectByName(projectName);
        IFile file = javaProject.getProject().getFile(conf.getName() + ".html"); //$NON-NLS-1$
        if (ExportUtil.validateEdit(shell, file)) // checkout file if required
        {
            // write build file
            String html = AppletUtil.buildHTMLFile(conf);
            InputStream is = new ByteArrayInputStream(html.getBytes("UTF-8")); //$NON-NLS-1$
            if (file.exists())
            {
                file.setContents(is, true, true, null);
            }
            else
            {
                file.create(is, true, null);
            }
        }
        
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, conf.getName());
        Element javaElement = doc.createElement("java"); //$NON-NLS-1$
        javaElement.setAttribute("fork", "yes");  //$NON-NLS-1$//$NON-NLS-2$
        javaElement.setAttribute("classname", conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_APPLET_APPLETVIEWER_CLASS, "sun.applet.AppletViewer")); //$NON-NLS-1$ //$NON-NLS-2$
        javaElement.setAttribute("failonerror", "true"); //$NON-NLS-1$ //$NON-NLS-2$
        if (value != null)
        {
            javaElement.setAttribute(IAntCoreConstants.DIR, ExportUtil.getRelativePath(dir, projectRoot));
        }
        addElement(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, IAntCoreConstants.EMPTY_STRING), doc, javaElement, "jvmarg", "line", variable2value, projectRoot);   //$NON-NLS-1$//$NON-NLS-2$
        addElement(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, IAntCoreConstants.EMPTY_STRING), doc, javaElement, "arg", "line", variable2value, projectRoot);   //$NON-NLS-1$//$NON-NLS-2$
        addElement(conf.getName() + ".html", doc, javaElement, "arg", "line", variable2value, projectRoot); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        element.appendChild(javaElement);
        addRuntimeClasspath(conf, javaElement);
        addRuntimeBootClasspath(conf, javaElement);
        root.appendChild(element);
    }
    
    /**
     * Convert JUnit launch configuration to JUnit task and add it to a document. 
     * @param variable2value    adds Eclipse variables to this map,
     *                             if run configuration makes use of this feature
     * @param conf                 applet configuration
     */
    public void addJUnit(Map variable2value, ILaunchConfiguration conf) throws CoreException
    {
        // <target name="runtest">
        //     <mkdir dir="junit"/>
        //     <junit fork="yes" printsummary="withOutAndErr">
        //         <formatter type="xml"/>
        //         <test name="testclass"/>
        //         <env key="a" value="b"/>
        //         <jvmarg value="-Dx=y"/>
        //         <classpath refid="project.classpath"/>
        //     </junit>
        // </target>
        String testClass = conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, IAntCoreConstants.EMPTY_STRING);
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, conf.getName());
        
        Element mkdirElement = doc.createElement("mkdir"); //$NON-NLS-1$
        mkdirElement.setAttribute(IAntCoreConstants.DIR, "${junit.output.dir}"); //$NON-NLS-1$
        element.appendChild(mkdirElement);
        
        Element junitElement = doc.createElement("junit"); //$NON-NLS-1$
        junitElement.setAttribute("fork", "yes"); //$NON-NLS-1$ //$NON-NLS-2$
        junitElement.setAttribute("printsummary", "withOutAndErr"); //$NON-NLS-1$ //$NON-NLS-2$
        String dir = conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, IAntCoreConstants.EMPTY_STRING);
        ExportUtil.addVariable(variable2value, dir, projectRoot);                
        if (!dir.equals(IAntCoreConstants.EMPTY_STRING))
        {
            junitElement.setAttribute(IAntCoreConstants.DIR, ExportUtil.getRelativePath(dir, projectRoot));
        }
        if (!conf.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true))
        {
            junitElement.setAttribute("newenvironment", "true"); //$NON-NLS-1$ //$NON-NLS-2$
        }
        Element formatterElement = doc.createElement("formatter"); //$NON-NLS-1$
        formatterElement.setAttribute("type", "xml");  //$NON-NLS-1$//$NON-NLS-2$
        junitElement.appendChild(formatterElement);
        if (!testClass.equals(IAntCoreConstants.EMPTY_STRING))
        {
            // Case 1: Single JUnit class
            Element testElement = doc.createElement("test"); //$NON-NLS-1$
            testElement.setAttribute(IAntCoreConstants.NAME, testClass);
            testElement.setAttribute("todir", "${junit.output.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
            junitElement.appendChild(testElement);                       
        }
        else
        {
            // Case 2: Run all tests in project, package or source folder
            String container = conf.getAttribute("org.eclipse.jdt.junit.CONTAINER" /*JUnitBaseLaunchConfiguration.LAUNCH_CONTAINER_ATTR*/, IAntCoreConstants.EMPTY_STRING); //$NON-NLS-1$
            IType[] types = ExportUtil.findTestsInContainer(container);
            Set sortedTypes = new TreeSet(ExportUtil.getITypeComparator());
            sortedTypes.addAll(Arrays.asList(types));
            for (Iterator iter = sortedTypes.iterator(); iter.hasNext();) {
                IType type = (IType) iter.next();
                Element testElement = doc.createElement("test"); //$NON-NLS-1$
                testElement.setAttribute(IAntCoreConstants.NAME, type.getFullyQualifiedName());
                testElement.setAttribute("todir", "${junit.output.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
                junitElement.appendChild(testElement);                       
            }
        }
        Map props = conf.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, new TreeMap());
        addElements(props, doc, junitElement, "env", "key", IAntCoreConstants.VALUE); //$NON-NLS-1$ //$NON-NLS-2$
        addElement(conf.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, IAntCoreConstants.EMPTY_STRING), doc, junitElement, "jvmarg", "line", variable2value, projectRoot); //$NON-NLS-1$ //$NON-NLS-2$
        element.appendChild(junitElement);
        addRuntimeClasspath(conf, junitElement);
        addRuntimeBootClasspath(conf, junitElement);
        root.appendChild(element);
    }
    
    /**
     * Add junitreport target. 
     */
    public void addJUnitReport()
    {
        variable2valueMap.put("junit.output.dir", JUNIT_OUTPUT_DIR); //$NON-NLS-1$
        
        // <target name="junitreport">
        //     <junitreport todir="junit">
        //         <fileset dir="junit">
        //             <include name="TEST-*.xml"/>
        //         </fileset>
        //         <report format="frames" todir="junit"/>
        //     </junitreport>
        // </target>
        Element element = doc.createElement("target"); //$NON-NLS-1$
        element.setAttribute(IAntCoreConstants.NAME, "junitreport"); //$NON-NLS-1$
        Element junitreport = doc.createElement("junitreport"); //$NON-NLS-1$
        junitreport.setAttribute("todir", "${junit.output.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
        Element fileset = doc.createElement("fileset"); //$NON-NLS-1$
        fileset.setAttribute(IAntCoreConstants.DIR, "${junit.output.dir}"); //$NON-NLS-1$
        junitreport.appendChild(fileset);
        Element include = doc.createElement("include"); //$NON-NLS-1$
        include.setAttribute(IAntCoreConstants.NAME, "TEST-*.xml"); //$NON-NLS-1$
        fileset.appendChild(include);
        Element report = doc.createElement("report"); //$NON-NLS-1$
        report.setAttribute("format", "frames"); //$NON-NLS-1$ //$NON-NLS-2$
        report.setAttribute("todir", "${junit.output.dir}"); //$NON-NLS-1$ //$NON-NLS-2$
        junitreport.appendChild(report);
        element.appendChild(junitreport);
        root.appendChild(element);
    }

    /**
     * Add classpath tag to given javaElement.
     */
    private void addRuntimeClasspath(ILaunchConfiguration conf, Element javaElement) throws CoreException
    {
        // <classpath refid="hello.classpath"/>
        Element classpathRefElement = doc.createElement("classpath"); //$NON-NLS-1$
        EclipseClasspath runtimeClasspath = new EclipseClasspath(project, conf, false);
        if (ExportUtil.isDefaultClasspath(project, runtimeClasspath))
        {
            classpathRefElement.setAttribute("refid", projectName + ".classpath"); //$NON-NLS-1$ //$NON-NLS-2$
        }
        else
        {
            String pathId = "run." + conf.getName() + ".classpath"; //$NON-NLS-1$ //$NON-NLS-2$
            classpathRefElement.setAttribute("refid", pathId); //$NON-NLS-1$
            createClasspaths(pathId, project, runtimeClasspath);            
        }
        javaElement.appendChild(classpathRefElement);
    }
    
    /**
     * Add bootclasspath tag to given javaElement.
     */
    private void addRuntimeBootClasspath(ILaunchConfiguration conf, Element javaElement) throws CoreException
    {        
        // <bootclasspath>
        //     <path refid="run.hello.bootclasspath"/>
        // </bootclasspath>
        EclipseClasspath bootClasspath = new EclipseClasspath(project, conf, true);
        if (bootClasspath.rawClassPathEntries.size() == 1
                && EclipseClasspath.isJreReference((String) bootClasspath.rawClassPathEntries.get(0))) {
            // the default boot classpath contains exactly one element (the JRE)
            return;
        }
        String pathId = "run." + conf.getName() + ".bootclasspath"; //$NON-NLS-1$ //$NON-NLS-2$
        createClasspaths(pathId, project, bootClasspath);
        Element bootclasspath = doc.createElement("bootclasspath"); //$NON-NLS-1$
        Element classpathRefElement = doc.createElement("path"); //$NON-NLS-1$
        classpathRefElement.setAttribute("refid", pathId); //$NON-NLS-1$
        bootclasspath.appendChild(classpathRefElement);
        javaElement.appendChild(bootclasspath);
    }

    /**
     * Create child node from <code>cmdLine</code> and add it to <code>element</code> which is part of
     * <code>doc</code>.
     * 
     * @param cmdLineArgs          command line arguments, separated with spaces or within double quotes, may also contain Eclipse variables 
     * @param doc                  XML document
     * @param element              node to add child to
     * @param elementName          name of new child node
     * @param attributeName        name of attribute for child node
     * @param variable2valueMap    adds Eclipse variables to this map,
     *                             if command line makes use of this feature
     */
    private static void addElement(String cmdLineArgs, Document doc,
            Element element, String elementName, String attributeName,
            Map variable2valueMap, String projectRoot) {

        if (cmdLineArgs == null || cmdLineArgs.length() == 0) {
            return;
        }
        ExportUtil.addVariable(variable2valueMap, cmdLineArgs, projectRoot);
        Element itemElement = doc.createElement(elementName);
        itemElement.setAttribute(attributeName, cmdLineArgs);
        element.appendChild(itemElement);            
    }
    
    /**
     * Create child nodes from string map and add them to <code>element</code> which is part of
     * <code>doc</code>.
     * 
     * @param map                   key/value string pairs
     * @param doc                   XML document
     * @param element               node to add children to
     * @param elementName           name of new child node
     * @param keyAttributeName      name of key attribute
     * @param valueAttributeName    name of value attribute
     */
    private static void addElements(Map map, Document doc, Element element, String elementName,
                                    String keyAttributeName, String valueAttributeName)
    {
        for (Iterator iter = map.keySet().iterator(); iter.hasNext();)
        {
            String key = (String) iter.next();
            String value = (String) map.get(key);
            Element itemElement = doc.createElement(elementName);
            itemElement.setAttribute(keyAttributeName, key);
            itemElement.setAttribute(valueAttributeName, value);
            element.appendChild(itemElement);            
        }
    }
    
    /**
     * Set config options.
     * @param buildfilename name for Ant buildfile
     * @param junitdir name of JUnit output directory
     * @param checkcycles check project for Ant compatibility
     * @param eclipsecompiler generate target for compiling project with Eclipse compiler
     */
    public static void setOptions(String buildfilename, String junitdir,
            boolean checkcycles, boolean eclipsecompiler) {

        if (buildfilename.length() > 0) {
            BUILD_XML = buildfilename;
        }
        if (junitdir.length() > 0) {
            JUNIT_OUTPUT_DIR = junitdir;
        }
        CHECK_SOURCE_CYCLES = checkcycles;
        CREATE_ECLIPSE_COMPILE_TARGET = eclipsecompiler;
    }
}