/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     G&H Softwareentwicklung GmbH - internationalization implementation (bug 150933)
 *******************************************************************************/

package org.eclipse.pde.internal.build.builder;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.build.Constants;
import org.eclipse.pde.internal.build.AbstractScriptGenerator;
import org.eclipse.pde.internal.build.BundleHelper;
import org.eclipse.pde.internal.build.Config;
import org.eclipse.pde.internal.build.IBuildPropertiesConstants;
import org.eclipse.pde.internal.build.IPDEBuildConstants;
import org.eclipse.pde.internal.build.IXMLConstants;
import org.eclipse.pde.internal.build.Messages;
import org.eclipse.pde.internal.build.ProductGenerator;
import org.eclipse.pde.internal.build.Utils;
import org.eclipse.pde.internal.build.ant.AntScript;
import org.eclipse.pde.internal.build.ant.FileSet;
import org.eclipse.pde.internal.build.site.BuildTimeFeature;

public class FeatureBuildScriptGenerator extends AbstractScriptGenerator {

	protected BuildTimeFeature feature;
	protected Properties buildProperties;
	//protected boolean analyseIncludedFeatures = false;
	//	private boolean generateJnlp = false;
	//	private boolean signJars = false;
	//	private String product = null;
	private BuildDirector director;
	private boolean generateProductFiles = true;

	protected String featureFolderName;
	protected String featureTempFolder;
	protected String featureFullName;
	protected String featureRootLocation;
	protected String sourceFeatureFullNameVersioned;

	private String customFeatureCallbacks = null;
	private String customCallbacksBuildpath = null;
	private String customCallbacksFailOnError = null;
	private String customCallbacksInheritAll = null;
	private BuildTimeFeature licenseFeature;

	public FeatureBuildScriptGenerator(BuildTimeFeature feature) {
		this.feature = feature;
		featureRootLocation = feature.getRootLocation();
	}

	public void setDirector(BuildDirector director) {
		this.director = director;
	}

	private void initializeVariables() throws CoreException {
		featureFullName = feature.getId() + "_" + feature.getVersion(); //$NON-NLS-1$
		featureFolderName = DEFAULT_FEATURE_LOCATION + '/' + featureFullName;
		featureTempFolder = Utils.getPropertyFormat(PROPERTY_FEATURE_TEMP_FOLDER);

		Properties properties = getBuildProperties();
		customFeatureCallbacks = properties.getProperty(PROPERTY_CUSTOM_BUILD_CALLBACKS);
		if (TRUE.equalsIgnoreCase(customFeatureCallbacks))
			customFeatureCallbacks = DEFAULT_CUSTOM_BUILD_CALLBACKS_FILE;
		else if (FALSE.equalsIgnoreCase(customFeatureCallbacks))
			customFeatureCallbacks = null;
		customCallbacksBuildpath = properties.getProperty(PROPERTY_CUSTOM_CALLBACKS_BUILDPATH, "."); //$NON-NLS-1$
		customCallbacksFailOnError = properties.getProperty(PROPERTY_CUSTOM_CALLBACKS_FAILONERROR, FALSE);
		customCallbacksInheritAll = properties.getProperty(PROPERTY_CUSTOM_CALLBACKS_INHERITALL);

		String sourceFeatureName = getBuildProperties().getProperty(PROPERTY_SOURCE_FEATURE_NAME);
		if (sourceFeatureName == null)
			sourceFeatureName = feature.getId().endsWith(".source") ? feature.getId() : feature.getId() + ".source"; //$NON-NLS-1$ //$NON-NLS-2$
		sourceFeatureFullNameVersioned = sourceFeatureName + "_" + feature.getVersion(); //$NON-NLS-1$

	}

	@Override
	public void generate() throws CoreException {
		if (feature.isBinary())
			return;

		if (getBuildProperties() == MissingProperties.getInstance() || AbstractScriptGenerator.getPropertyAsBoolean(IBuildPropertiesConstants.PROPERTY_PACKAGER_MODE)) {
			feature.setBinary(true);
			return;
		}

		initializeVariables();

		// if the feature defines its own custom script, we do not generate a
		// new one but we do try to update the version number
		boolean custom = TRUE.equalsIgnoreCase((String) getBuildProperties().get(PROPERTY_CUSTOM));
		File customBuildFile = null;
		if (custom) {
			customBuildFile = new File(featureRootLocation, DEFAULT_BUILD_SCRIPT_FILENAME);
			if (!customBuildFile.exists()) {
				String message = NLS.bind(Messages.error_missingCustomBuildFile, customBuildFile);
				throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_WRITING_SCRIPT, message, null));
			}
			/* need to do root files here because we won't be doing the gatherBinParts where it normally happens */
			List<Config> configs = getConfigInfos();
			for (Config config : configs) {
				director.getAssemblyData().addRootFileProvider(config, feature);
			}

			//Feature had a custom build script, we need to update the version in it.
			//Do it here after generateChildrenScripts since there may have been a suffix generated.
			try {
				Utils.updateVersion(customBuildFile, PROPERTY_FEATURE_VERSION_SUFFIX, feature.getVersion());
			} catch (IOException e) {
				String message = NLS.bind(Messages.exception_writeScript, customBuildFile);
				throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_WRITING_SCRIPT, message, e));
			}
		} else {
			openScript(featureRootLocation, DEFAULT_BUILD_SCRIPT_FILENAME);
			try {
				generateBuildScript();
			} finally {
				closeScript();
			}
		}
	}

	/**
	 * Main call for generating the script.
	 */
	private void generateBuildScript() throws CoreException {
		if (BundleHelper.getDefault().isDebugging())
			System.out.println("Generating feature " + featureFullName); //$NON-NLS-1$
		generatePrologue();
		generateAllPluginsTarget();
		generateAllFeaturesTarget();
		generateUpdateFeatureFile();
		generateAllChildrenTarget();
		generateChildrenTarget();
		generateBuildJarsTarget();
		generateBuildZipsTarget();
		generateBuildUpdateJarTarget();

		generatePublishBinPartsTarget();
		generateGatherBinPartsTarget();
		generateZipDistributionWholeTarget();
		generateZipSourcesTarget();
		generateZipLogsTarget();
		generateCleanTarget();
		generateRefreshTarget();
		generateGatherSourcesTarget();
		generateGatherLogsTarget();
		generateEpilogue();
	}

	/**
	 * Method generateGatherSource. Used to enable the recursive call of
	 * gathering the sources for the features
	 */
	private void generateGatherSourcesTarget() {
		script.printTargetDeclaration(TARGET_GATHER_SOURCES, null, null, null, null);
		Map<String, String> params = new HashMap<>(2);
		params.put(PROPERTY_DESTINATION_TEMP_FOLDER, Utils.getPropertyFormat(PROPERTY_FEATURE_TEMP_FOLDER) + '/' + DEFAULT_PLUGIN_LOCATION + '/' + sourceFeatureFullNameVersioned + '/' + "src"); //$NON-NLS-1$
		params.put(PROPERTY_TARGET, TARGET_GATHER_SOURCES);
		script.printAntCallTask(TARGET_CHILDREN, true, params);
		script.printTargetEnd();
	}

	/**
	 * Method generateGatherSource. Used to enable the recursive call of
	 * gathering the sources for the features
	 */
	private void generateGatherLogsTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_GATHER_LOGS, TARGET_INIT, null, null, null);
		String destinationTempFolder = IPath.fromOSString(featureTempFolder).append(DEFAULT_PLUGIN_LOCATION).toString();
		script.printMkdirTask(destinationTempFolder);
		script.printProperty(PROPERTY_DESTINATION_TEMP_FOLDER, destinationTempFolder);
		script.printConditionIsSet(PROPERTY_LOG_EXTENSION_PARAM, PROPERTY_LOG_EXTENSION, PROPERTY_LOG_EXTENSION, PROPERTY_LOG_EXTENSION_PARAM);
		Map<String, String> params = new HashMap<>(3);
		params.put(PROPERTY_TARGET, TARGET_GATHER_LOGS);
		params.put(PROPERTY_DESTINATION_TEMP_FOLDER, destinationTempFolder);
		params.put(Utils.getPropertyFormat(PROPERTY_LOG_EXTENSION_PARAM), Utils.getPropertyFormat(PROPERTY_LOG_EXTENSION));
		script.printAntCallTask(TARGET_ALL_CHILDREN, false, params);
		script.printTargetEnd();
	}

	private void generateUpdateFeatureFile() {
		script.printTargetDeclaration(TARGET_UPDATE_FEATURE_FILE, TARGET_INIT, null, null, null);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>build.zips</code> target to the given Ant script.
	 */
	private void generateBuildZipsTarget() throws CoreException {
		StringBuffer zips = new StringBuffer();
		Properties props = getBuildProperties();
		for (Entry<Object, Object> entry : props.entrySet()) {
			String key = (String) entry.getKey();
			if (key.startsWith(PROPERTY_SOURCE_PREFIX) && key.endsWith(PROPERTY_ZIP_SUFFIX)) {
				String zipName = key.substring(PROPERTY_SOURCE_PREFIX.length());
				zips.append(',');
				zips.append(zipName);
				generateZipIndividualTarget(zipName, (String) entry.getValue());
			}
		}
		script.println();
		script.printTargetDeclaration(TARGET_BUILD_ZIPS, TARGET_INIT + zips.toString(), null, null, null);
		Map<String, String> params = new HashMap<>(2);
		params.put(PROPERTY_TARGET, TARGET_BUILD_ZIPS);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printTargetEnd();
	}

	/**
	 * Add a <code>zip</code> target to the given Ant script.
	 * 
	 * @param zipName the name of the zip file to create
	 * @param source the directory name to read the files from
	 */
	private void generateZipIndividualTarget(String zipName, String source) {
		script.println();
		script.printTargetDeclaration(zipName, TARGET_INIT, null, null, null);
		script.printZipTask(Utils.getPropertyFormat(PROPERTY_BASEDIR) + '/' + zipName, Utils.getPropertyFormat(PROPERTY_BASEDIR) + '/' + source, false, false, null);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>clean</code> target to the given Ant script.
	 */
	private void generateCleanTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_CLEAN, TARGET_INIT, null, null, NLS.bind(Messages.build_feature_clean, feature.getId()));
		script.printDeleteTask(null, Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".jar", null); //$NON-NLS-1$
		script.printDeleteTask(null, Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".bin.dist.zip", null); //$NON-NLS-1$
		script.printDeleteTask(null, Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".log.zip", null); //$NON-NLS-1$
		script.printDeleteTask(null, Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".src.zip", null); //$NON-NLS-1$
		script.printDeleteTask(featureTempFolder, null, null);
		Map<String, String> params = new HashMap<>(2);
		params.put(PROPERTY_TARGET, TARGET_CLEAN);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>zip.logs</code> target to the given Ant script.
	 */
	private void generateZipLogsTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_ZIP_LOGS, TARGET_INIT, null, null, null);
		script.printDeleteTask(featureTempFolder, null, null);
		script.printMkdirTask(featureTempFolder);
		script.printConditionIsSet(PROPERTY_LOG_EXTENSION_PARAM, PROPERTY_LOG_EXTENSION, PROPERTY_LOG_EXTENSION, PROPERTY_LOG_EXTENSION_PARAM);
		Map<String, String> params = new HashMap<>(1);
		params.put(PROPERTY_INCLUDE_CHILDREN, "true"); //$NON-NLS-1$
		params.put(PROPERTY_TARGET, TARGET_GATHER_LOGS);
		params.put(PROPERTY_DESTINATION_TEMP_FOLDER, IPath.fromOSString(featureTempFolder).append(DEFAULT_PLUGIN_LOCATION).toString());
		params.put(Utils.getPropertyFormat(PROPERTY_LOG_EXTENSION_PARAM), Utils.getPropertyFormat(PROPERTY_LOG_EXTENSION));
		script.printAntCallTask(TARGET_ALL_CHILDREN, false, params);
		IPath destination = IPath.fromOSString(Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION)).append(featureFullName + ".log.zip"); //$NON-NLS-1$
		script.printZipTask(destination.toString(), featureTempFolder, true, false, null);
		script.printDeleteTask(featureTempFolder, null, null);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>zip.sources</code> target to the given Ant script.
	 */
	protected void generateZipSourcesTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_ZIP_SOURCES, TARGET_INIT, null, null, null);
		script.printDeleteTask(featureTempFolder, null, null);
		script.printMkdirTask(featureTempFolder);
		Map<String, String> params = new HashMap<>(3);
		params.put(PROPERTY_INCLUDE_CHILDREN, "true"); //$NON-NLS-1$
		params.put(PROPERTY_TARGET, TARGET_GATHER_SOURCES);
		params.put(PROPERTY_DESTINATION_TEMP_FOLDER, featureTempFolder + '/' + DEFAULT_PLUGIN_LOCATION + '/' + sourceFeatureFullNameVersioned + '/' + "src"); //$NON-NLS-1$
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printZipTask(Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".src.zip", featureTempFolder, true, false, null); //$NON-NLS-1$
		script.printDeleteTask(featureTempFolder, null, null);
		script.printTargetEnd();
	}

	private void generatePublishBinPartsTarget() throws CoreException {
		Properties properties = getBuildProperties();
		Map<String, Map<String, String>> root = Utils.processRootProperties(properties, true);
		Map<String, String> common = root.get(Utils.ROOT_COMMON);
		for (Config aConfig : getConfigInfos()) {
			String configKey = aConfig.toString("."); //$NON-NLS-1$
			if (root.containsKey(configKey) || common.size() > 0)
				director.getAssemblyData().addRootFileProvider(aConfig, feature);
		}

		script.println();
		script.printTargetDeclaration(TARGET_PUBLISH_BIN_PARTS, TARGET_INIT, PROPERTY_P2_PUBLISH_PARTS, null, null);

		String featureTemp = Utils.getPropertyFormat(PROPERTY_FEATURE_TEMP_FOLDER) + '/' + featureFolderName;
		script.printMkdirTask(featureTemp);

		Map<String, String> callbackParams = null;
		if (customFeatureCallbacks != null) {
			callbackParams = new HashMap<>(2);
			callbackParams.put(PROPERTY_DESTINATION_TEMP_FOLDER, IPath.fromOSString(Utils.getPropertyFormat(PROPERTY_FEATURE_BASE)).append(DEFAULT_PLUGIN_LOCATION).toString());
			callbackParams.put(PROPERTY_FEATURE_DIRECTORY, featureTemp);
			script.printSubantTask(Utils.getPropertyFormat(PROPERTY_CUSTOM_BUILD_CALLBACKS), PROPERTY_PRE + TARGET_GATHER_BIN_PARTS, customCallbacksBuildpath, customCallbacksFailOnError, customCallbacksInheritAll, callbackParams, null);
		}

		String include = (String) getBuildProperties().get(PROPERTY_BIN_INCLUDES);
		if (include == null || include.indexOf("feature.xml") == -1) //$NON-NLS-1$
			include = (include != null ? include + "," : "") + "feature.xml"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

		String exclude = (String) getBuildProperties().get(PROPERTY_BIN_EXCLUDES);

		FileSet fileSet = new FileSet(Utils.getPropertyFormat(PROPERTY_BASEDIR), null, include, null, exclude, null, null);
		script.printCopyTask(null, featureTemp, new FileSet[] {fileSet}, true, true);

		generateIdReplacerCall(featureTemp);
		generateLicenseReplacerCall(featureTemp, customFeatureCallbacks != null);

		if (customFeatureCallbacks != null) {
			script.printSubantTask(Utils.getPropertyFormat(PROPERTY_CUSTOM_BUILD_CALLBACKS), PROPERTY_POST + TARGET_GATHER_BIN_PARTS, customCallbacksBuildpath, customCallbacksFailOnError, customCallbacksInheritAll, callbackParams, null);
		}

		script.println("<eclipse.gatherFeature "); //$NON-NLS-1$
		script.println("   metadataRepository=\"" + Utils.getPropertyFormat(PROPERTY_P2_BUILD_REPO) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		script.println("   artifactRepository=\"" + Utils.getPropertyFormat(PROPERTY_P2_BUILD_REPO) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		if (customFeatureCallbacks != null)
			script.println("   targetFolder=\"" + featureTemp + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		else
			script.println("   buildResultFolder=\"" + featureTemp + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		script.println("   baseDirectory=\"${basedir}\""); //$NON-NLS-1$
		if (getLicenseFeature() != null) {
			IPath licenseLocation = Utils.makeRelative(IPath.fromOSString(getLicenseFeatureRootLocation()), IPath.fromOSString(featureRootLocation));
			String licensePath = licenseLocation.isAbsolute() ? licenseLocation.toString() : "${basedir}/" + licenseLocation.toString(); //$NON-NLS-1$
			script.println("   licenseDirectory=\"" + licensePath + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		script.println("/>"); //$NON-NLS-1$

		script.printTargetEnd();
	}

	/**
	 * Add the <code>gather.bin.parts</code> target to the given Ant script
	 */
	private void generateGatherBinPartsTarget() throws CoreException {
		String include = (String) getBuildProperties().get(PROPERTY_BIN_INCLUDES);
		String exclude = (String) getBuildProperties().get(PROPERTY_BIN_EXCLUDES);
		String root = Utils.getPropertyFormat(PROPERTY_FEATURE_BASE) + '/' + featureFolderName;

		script.println();
		script.printTargetDeclaration(TARGET_GATHER_BIN_PARTS, TARGET_INIT, PROPERTY_FEATURE_BASE, null, null);
		if (include != null)
			script.printMkdirTask(root);

		Map<String, String> callbackParams = null;
		if (customFeatureCallbacks != null) {
			callbackParams = new HashMap<>(2);
			callbackParams.put(PROPERTY_DESTINATION_TEMP_FOLDER, IPath.fromOSString(Utils.getPropertyFormat(PROPERTY_FEATURE_BASE)).append(DEFAULT_PLUGIN_LOCATION).toString());
			callbackParams.put(PROPERTY_FEATURE_DIRECTORY, root);
			script.printSubantTask(Utils.getPropertyFormat(PROPERTY_CUSTOM_BUILD_CALLBACKS), PROPERTY_PRE + TARGET_GATHER_BIN_PARTS, customCallbacksBuildpath, customCallbacksFailOnError, customCallbacksInheritAll, callbackParams, null);
		}

		Map<String, String> params = new HashMap<>(2);
		params.put(PROPERTY_TARGET, TARGET_GATHER_BIN_PARTS);
		params.put(PROPERTY_DESTINATION_TEMP_FOLDER, IPath.fromOSString(Utils.getPropertyFormat(PROPERTY_FEATURE_BASE)).append(DEFAULT_PLUGIN_LOCATION).toString());
		script.printAntCallTask(TARGET_CHILDREN, true, params);

		if (include != null) {
			FileSet fileSet = new FileSet(Utils.getPropertyFormat(PROPERTY_BASEDIR), null, include, null, exclude, null, null);
			script.printCopyTask(null, root, new FileSet[] {fileSet}, true, false);

			// Generate the parameters for the Id Replacer.
			generateIdReplacerCall(root);
			generateLicenseReplacerCall(root, true);
		}
		generateRootFilesAndPermissionsCalls();
		if (customFeatureCallbacks != null) {
			script.printSubantTask(Utils.getPropertyFormat(PROPERTY_CUSTOM_BUILD_CALLBACKS), PROPERTY_POST + TARGET_GATHER_BIN_PARTS, customCallbacksBuildpath, customCallbacksFailOnError, customCallbacksInheritAll, callbackParams, null);
		}
		script.printTargetEnd();
		generateRootFilesAndPermissions();
	}

	private void generateIdReplacerCall(String root) throws CoreException {
		String featureVersionInfo = Utils.getEntryVersionMappings(feature.getRawIncludedFeatureReferences(), getSite(false));
		String pluginVersionInfo = Utils.getEntryVersionMappings(feature.getRawPluginEntries(), getSite(false));

		script.println("<eclipse.idReplacer featureFilePath=\"" + AntScript.getEscaped(root) + '/' + Constants.FEATURE_FILENAME_DESCRIPTOR + "\"  selfVersion=\"" + feature.getVersion() + "\" featureIds=\"" + featureVersionInfo + "\" pluginIds=\"" + pluginVersionInfo + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
	}

	private String getLicenseFeatureRootLocation() throws CoreException {
		if (getLicenseFeature() != null) {
			return getLicenseFeature().getRootLocation();
		}
		return ""; //$NON-NLS-1$
	}

	private BuildTimeFeature getLicenseFeature() throws CoreException {
		if (licenseFeature == null) {
			String licenseFeatureName = feature.getLicenseFeature();
			if (licenseFeatureName == null || licenseFeatureName.length() == 0) {
				return null;
			}
			licenseFeature = getSite(false).findFeature(licenseFeatureName, feature.getLicenseFeatureVersion(), true);
		}
		return licenseFeature;
	}

	private void generateLicenseReplacerCall(String featureRoot, boolean printCopy) throws CoreException {
		if (getLicenseFeature() == null) {
			return;
		}

		IPath licenseLocation = Utils.makeRelative(IPath.fromOSString(getLicenseFeatureRootLocation()), IPath.fromOSString(featureRootLocation));
		String licensePath = licenseLocation.isAbsolute() ? licenseLocation.toString() : "${basedir}/" + licenseLocation.toString(); //$NON-NLS-1$

		if (printCopy) {
			String include, exclude;
			if (getLicenseFeature().isBinary()) {
				include = "**"; //$NON-NLS-1$
				exclude = "META-INF/"; //$NON-NLS-1$
			} else {
				include = (String) getBuildProperties(getLicenseFeatureRootLocation()).get(PROPERTY_BIN_INCLUDES);
				exclude = (String) getBuildProperties(getLicenseFeatureRootLocation()).get(PROPERTY_BIN_EXCLUDES);
			}
			exclude = (exclude != null ? exclude + "," : "") + IPDEBuildConstants.LICENSE_DEFAULT_EXCLUDES; //$NON-NLS-1$//$NON-NLS-2$

			FileSet fileSet = new FileSet(licensePath, null, include, null, exclude, null, null);
			script.printCopyTask(null, featureRoot, new FileSet[] {fileSet}, true, true);
		}

		script.println("<eclipse.licenseReplacer featureFilePath=\"" + AntScript.getEscaped(featureRoot) + "\" licenseFilePath=\"" + AntScript.getEscaped(licensePath) + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	private void generateRootFilesAndPermissionsCalls() {
		Map<String, String> param = new HashMap<>(1);
		param.put(TARGET_ROOT_TARGET, TARGET_ROOTFILES_PREFIX + Utils.getPropertyFormat(PROPERTY_OS) + '_' + Utils.getPropertyFormat(PROPERTY_WS) + '_' + Utils.getPropertyFormat(PROPERTY_ARCH));
		script.printAntCallTask(TARGET_ROOTFILES_PREFIX, true, param);
	}

	private void generateRootFilesAndPermissions() throws CoreException {
		boolean generateEclipseProduct = Boolean.valueOf(getBuildProperties().getProperty(IBuildPropertiesConstants.PROPERTY_GENERATE_ECLIPSEPRODUCT)).booleanValue();
		String product = (generateProductFiles || generateEclipseProduct) ? director.getProduct() : null;
		if (product != null) {
			ProductGenerator generator = new ProductGenerator();
			generator.setProduct(product);
			generator.setBuildSiteFactory(siteFactory);
			generator.setBuildProperties(getBuildProperties());
			generator.setRoot(featureRootLocation);
			generator.setWorkingDirectory(getWorkingDirectory());
			generator.setAssemblyInfo(director.getAssemblyData());
			try {
				if (!BuildDirector.p2Gathering && generateProductFiles)
					generator.generate();
				else if (generateEclipseProduct)
					generator.generateEclipseProduct();
			} catch (CoreException e) {
				//problem with the .product file
				//TODO Log warning/error
			}
		}

		script.printTargetDeclaration(TARGET_ROOTFILES_PREFIX, null, null, PROPERTY_OMIT_ROOTFILES, null);
		script.printAntCallTask(Utils.getPropertyFormat(TARGET_ROOT_TARGET), true, null);
		script.printTargetEnd();
		script.println();

		for (Config aConfig : getConfigInfos()) {
			script.printTargetDeclaration(TARGET_ROOTFILES_PREFIX + aConfig.toString("_"), null, null, null, null); //$NON-NLS-1$
			generateCopyRootFiles(aConfig);
			Utils.generatePermissions(getBuildProperties(), aConfig, PROPERTY_FEATURE_BASE, script);
			script.printTargetEnd();
		}
		script.printTargetDeclaration(TARGET_ROOTFILES_PREFIX + "group_group_group", null, null, null, null); //$NON-NLS-1$
		for (Config aConfig : getConfigInfos()) {
			script.printAntCallTask(TARGET_ROOTFILES_PREFIX + aConfig.toString("_"), true, null);//.getPropertyFormat(PROPERTY_OS) + '_' + Utils.getPropertyFormat(PROPERTY_WS) + '_' + Utils.getPropertyFormat(PROPERTY_ARCH)) //$NON-NLS-1$
		}
		script.printTargetEnd();
	}

	private void generateCopyRootFiles(Config aConfig) throws CoreException {
		Properties properties = getBuildProperties();
		String configKey = aConfig.toString("."); //$NON-NLS-1$
		Map<String, Map<String, String>> root = Utils.processRootProperties(properties, true);
		Map<String, String> foldersToCopy = root.containsKey(configKey) ? root.get(configKey) : root.get(Utils.ROOT_COMMON);
		if (foldersToCopy == null || foldersToCopy.isEmpty())
			return;

		String configName = aConfig.toStringReplacingAny(".", ANY_STRING); //$NON-NLS-1$
		String shouldOverwrite = properties.getProperty(PROPERTY_OVERWRITE_ROOTFILES, "true"); //$NON-NLS-1$
		boolean overwrite = Boolean.valueOf(shouldOverwrite).booleanValue();

		director.getAssemblyData().addRootFileProvider(aConfig, feature);

		Object[] folders = foldersToCopy.keySet().toArray();
		String fileList = null;
		for (Object folder2 : folders) {
			String folder = (String) folder2;
			if (folder.equals(Utils.ROOT_LINK) || folder.startsWith(Utils.ROOT_PERMISSIONS))
				continue;
			fileList = foldersToCopy.get(folder);
			String[] files = Utils.getArrayFromString(fileList, ","); //$NON-NLS-1$
			FileSet[] fileSet = new FileSet[files.length];
			for (int j = 0; j < files.length; j++) {
				String file = files[j];
				String fromDir = Utils.getPropertyFormat(PROPERTY_BASEDIR) + '/';
				if (file.startsWith("absolute:")) { //$NON-NLS-1$
					file = file.substring(9);
					fromDir = ""; //$NON-NLS-1$
				} else if (file.startsWith("license:")) { //$NON-NLS-1$
					if (getLicenseFeature() == null) {
						throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_PLUGIN_MISSING, NLS.bind(Messages.error_licenseRootWithoutLicenseRef, featureRootLocation), null));
					}
					file = file.substring(8);
					fromDir = getLicenseFeatureRootLocation();
				}
				if (file.startsWith("file:")) { //$NON-NLS-1$
					IPath target = IPath.fromOSString(file.substring(5));
					fileSet[j] = new FileSet(fromDir + target.removeLastSegments(1), null, target.lastSegment(), null, null, null, null);
				} else {
					fileSet[j] = new FileSet(fromDir + file, null, "**", null, null, null, null); //$NON-NLS-1$
				}
			}
			if (fileSet.length > 0) {
				script.printMkdirTask(Utils.getPropertyFormat(PROPERTY_FEATURE_BASE) + '/' + configName + '/' + Utils.getPropertyFormat(PROPERTY_COLLECTING_FOLDER) + '/' + folder);
				script.printCopyTask(null, Utils.getPropertyFormat(PROPERTY_FEATURE_BASE) + '/' + configName + '/' + Utils.getPropertyFormat(PROPERTY_COLLECTING_FOLDER) + '/' + folder, fileSet, true, overwrite);
			}
		}
	}

	/**
	 * Add the <code>build.update.jar</code> target to the given script.
	 */
	private void generateBuildUpdateJarTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_BUILD_UPDATE_JAR, TARGET_INIT, null, null, NLS.bind(Messages.build_feature_buildUpdateJar, feature.getId()));
		Map<String, String> params = new HashMap<>(1);
		params.put(PROPERTY_TARGET, TARGET_BUILD_UPDATE_JAR);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printProperty(PROPERTY_FEATURE_BASE, featureTempFolder);
		script.printDeleteTask(featureTempFolder, null, null);
		script.printMkdirTask(featureTempFolder);
		script.printMkdirTask(featureTempFolder + '/' + featureFolderName);
		params.clear();
		params.put(PROPERTY_FEATURE_BASE, featureTempFolder);
		params.put(PROPERTY_OS, feature.getOS() == null ? Config.ANY : feature.getOS());
		params.put(PROPERTY_WS, feature.getWS() == null ? Config.ANY : feature.getWS());
		params.put(PROPERTY_ARCH, feature.getArch() == null ? Config.ANY : feature.getArch());
		params.put(PROPERTY_NL, feature.getNL() == null ? Config.ANY : feature.getNL());
		params.put(PROPERTY_OMIT_ROOTFILES, "true"); //$NON-NLS-1$

		// Be sure to call the gather with children turned off. The only way to
		// do this is
		// to clear all inherited values. Must remember to setup anything that
		// is really expected.
		script.printAntCallTask(TARGET_GATHER_BIN_PARTS, false, params);
		String jar = Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".jar"; //$NON-NLS-1$
		script.printJarTask(jar, featureTempFolder + '/' + featureFolderName, null);
		script.printDeleteTask(featureTempFolder, null, null);
		if (director.getGenerateJnlp())
			script.println("<eclipse.jnlpGenerator feature=\"" + AntScript.getEscaped(jar) + "\"  codebase=\"" + Utils.getPropertyFormat(IXMLConstants.PROPERTY_JNLP_CODEBASE) + "\" j2se=\"" + Utils.getPropertyFormat(IXMLConstants.PROPERTY_JNLP_J2SE) + "\" locale=\"" + Utils.getPropertyFormat(IXMLConstants.PROPERTY_JNLP_LOCALE) + "\" generateOfflineAllowed=\"" + Utils.getPropertyFormat(PROPERTY_JNLP_GENOFFLINE) + "\" configInfo=\"" + Utils.getPropertyFormat(PROPERTY_JNLP_CONFIGS) + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ ); 
		if (director.getSignJars()) {
			if (director.getGenerateJnlp()) {
				script.printProperty(PROPERTY_UNSIGN, "true"); //$NON-NLS-1$
			}
			script.println("<eclipse.jarProcessor sign=\"" + Utils.getPropertyFormat(PROPERTY_SIGN) + "\" unsign=\"" + Utils.getPropertyFormat(PROPERTY_UNSIGN) + "\" jar=\"" + AntScript.getEscaped(jar) + "\" alias=\"" + Utils.getPropertyFormat(PROPERTY_SIGN_ALIAS) + "\" keystore=\"" + Utils.getPropertyFormat(PROPERTY_SIGN_KEYSTORE) + "\" storepass=\"" + Utils.getPropertyFormat(PROPERTY_SIGN_STOREPASS) + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
		}

		script.printTargetEnd();
	}

	/**
	 * Add the <code>zip.distribution</code> target to the given Ant script.
	 * Zip up the whole feature.
	 */
	protected void generateZipDistributionWholeTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_ZIP_DISTRIBUTION, TARGET_INIT, null, null, NLS.bind(Messages.build_feature_zips, feature.getId()));
		script.printDeleteTask(featureTempFolder, null, null);
		script.printMkdirTask(featureTempFolder);
		Map<String, String> params = new HashMap<>(1);
		params.put(PROPERTY_FEATURE_BASE, featureTempFolder);
		params.put(PROPERTY_INCLUDE_CHILDREN, TRUE);
		params.put(PROPERTY_OS, feature.getOS() == null ? Config.ANY : feature.getOS());
		params.put(PROPERTY_WS, feature.getWS() == null ? Config.ANY : feature.getWS());
		params.put(PROPERTY_ARCH, feature.getArch() == null ? Config.ANY : feature.getArch());
		params.put(PROPERTY_NL, feature.getNL() == null ? Config.ANY : feature.getNL());
		script.printAntCallTask(TARGET_GATHER_BIN_PARTS, true, params);
		script.printZipTask(Utils.getPropertyFormat(PROPERTY_FEATURE_DESTINATION) + '/' + featureFullName + ".bin.dist.zip", featureTempFolder, false, false, null); //$NON-NLS-1$
		script.printDeleteTask(featureTempFolder, null, null);
		script.printTargetEnd();
	}

	/**
	 * Executes a given target in all children's script files.
	 */
	private void generateAllChildrenTarget() {
		StringBuffer depends = new StringBuffer();
		depends.append(TARGET_INIT);
		depends.append(',');
		depends.append(TARGET_ALL_FEATURES);
		depends.append(',');
		depends.append(TARGET_ALL_PLUGINS);
		depends.append(',');
		depends.append(TARGET_UPDATE_FEATURE_FILE);
		script.println();
		script.printTargetDeclaration(TARGET_ALL_CHILDREN, depends.toString(), null, null, null);
		script.printTargetEnd();
	}

	/**
	 * Target responsible for delegating target calls to plug-in's build.xml
	 * scripts. Plugins are sorted according to the requires chain. Fragments
	 * are inserted afterward
	 */
	protected void generateAllPluginsTarget() throws CoreException {
		Set<BundleDescription> plugins = computeElements();
		List<BundleDescription> sortedPlugins = Utils.extractPlugins(getSite(false).getRegistry().getSortedBundles(), plugins);
		script.println();
		script.printTargetDeclaration(TARGET_ALL_PLUGINS, TARGET_INIT, null, null, null);
		Set<BundleDescription> writtenCalls = new HashSet<>(sortedPlugins.size());
		for (BundleDescription current : sortedPlugins) {
			//If it is not a compiled element, then we don't generate a call
			Properties bundleProperties = (Properties) current.getUserObject();
			if (bundleProperties == null || bundleProperties.get(IS_COMPILED) == null || bundleProperties.get(IS_COMPILED) == Boolean.FALSE)
				continue;
			// Get the os / ws / arch to pass as a parameter to the plugin
			if (writtenCalls.contains(current))
				continue;
			writtenCalls.add(current);
			FeatureEntry[] entries = Utils.getPluginEntry(feature, current.getSymbolicName(), false); //TODO This can be improved to use the value from the user object in the bundleDescription
			for (FeatureEntry entry : entries) {
				List<Config> list = director.selectConfigs(entry);
				if (list.size() == 0)
					continue;
				Map<String, String> params = null;
				Config aMatchingConfig = list.get(0);
				params = new HashMap<>(3);
				if (!aMatchingConfig.getOs().equals(Config.ANY))
					params.put(PROPERTY_OS, aMatchingConfig.getOs());
				if (!aMatchingConfig.getWs().equals(Config.ANY))
					params.put(PROPERTY_WS, aMatchingConfig.getWs());
				if (!aMatchingConfig.getArch().equals(Config.ANY))
					params.put(PROPERTY_ARCH, aMatchingConfig.getArch());
				IPath location = Utils.makeRelative(IPath.fromOSString(getLocation(current)), IPath.fromOSString(featureRootLocation));
				script.printAntTask(DEFAULT_BUILD_SCRIPT_FILENAME, location.toString(), Utils.getPropertyFormat(PROPERTY_TARGET), null, null, params);
			}
		}
		script.printTargetEnd();
	}

	protected Set<BundleDescription> computeElements() throws CoreException {
		Set<BundleDescription> computedElements = new LinkedHashSet<>(5);
		FeatureEntry[] pluginList = feature.getPluginEntries();
		for (FeatureEntry entry : pluginList) {
			BundleDescription model;
			if (director.selectConfigs(entry).size() == 0)
				continue;

			String versionRequested = entry.getVersion();
			model = getSite(false).getRegistry().getResolvedBundle(entry.getId(), versionRequested);
			if (model == null) {
				String message = NLS.bind(Messages.exception_missingPlugin, entry.getId() + "_" + entry.getVersion()); //$NON-NLS-1$
				throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_PLUGIN_MISSING, message, null));
			}

			computedElements.add(model);
		}
		return computedElements;
	}

	private void generateAllFeaturesTarget() throws CoreException {
		script.printTargetDeclaration(TARGET_ALL_FEATURES, TARGET_INIT, null, null, null);
		//if (analyseIncludedFeatures) {
		FeatureEntry[] features = feature.getIncludedFeatureReferences();
		for (FeatureEntry feature2 : features) {
			String featureId = feature2.getId();
			String versionId = feature2.getVersion();
			BuildTimeFeature includedFeature = getSite(false).findFeature(featureId, versionId, false);
			if (includedFeature == null) {
				if (feature2.isOptional())
					continue;
				String message = NLS.bind(Messages.exception_missingFeature, featureId + ' ' + versionId);
				throw new CoreException(new Status(IStatus.ERROR, PI_PDEBUILD, EXCEPTION_FEATURE_MISSING, message, null));
			}

			if (includedFeature.isBinary())
				continue;

			String includedFeatureDirectory = includedFeature.getRootLocation();
			IPath location = Utils.makeRelative(IPath.fromOSString(includedFeatureDirectory), IPath.fromOSString(featureRootLocation));
			script.printAntTask(DEFAULT_BUILD_SCRIPT_FILENAME, location.toString(), Utils.getPropertyFormat(PROPERTY_TARGET), null, null, null);
		}
		//}
		script.printTargetEnd();
	}

	/**
	 * Just ends the script.
	 */
	private void generateEpilogue() {
		script.println();
		script.printProjectEnd();
	}

	/**
	 * Defines, the XML declaration, Ant project and init target.
	 */
	private void generatePrologue() {
		script.printProjectDeclaration(feature.getId(), TARGET_BUILD_UPDATE_JAR, "."); //$NON-NLS-1$
		script.println();
		script.printTargetDeclaration(TARGET_INIT, null, null, null, null);
		script.printProperty(PROPERTY_FEATURE_TEMP_FOLDER, Utils.getPropertyFormat(PROPERTY_BASEDIR) + '/' + PROPERTY_FEATURE_TEMP_FOLDER);
		script.printProperty(PROPERTY_FEATURE_DESTINATION, Utils.getPropertyFormat(PROPERTY_BASEDIR));
		script.printProperty(PROPERTY_P2_BUILD_REPO, "file:" + Utils.getPropertyFormat(PROPERTY_BUILD_DIRECTORY) + "/buildRepo"); //$NON-NLS-1$ //$NON-NLS-2$
		script.printConditionIsTrue(PROPERTY_P2_PUBLISH_PARTS, TRUE, Utils.getPropertyFormat(PROPERTY_P2_GATHERING));
		if (customFeatureCallbacks != null) {
			script.printAvailableTask(PROPERTY_CUSTOM_BUILD_CALLBACKS, customCallbacksBuildpath + '/' + customFeatureCallbacks, customFeatureCallbacks);
		}
		script.printTargetEnd();
	}

	/**
	 * Return a properties object constructed from the build.properties file
	 * for the given feature. If no file exists, then an empty properties
	 * object is returned.
	 * 
	 * @return Properties the feature's build.properties
	 */
	protected Properties getBuildProperties() throws CoreException {
		if (buildProperties == null)
			buildProperties = readProperties(featureRootLocation, PROPERTIES_FILE, director.isIgnoreMissingPropertiesFile() ? IStatus.OK : IStatus.WARNING);
		return buildProperties;
	}

	/**
	 * Return a properties object constructed from the build.properties file
	 * for the given feature. If no file exists, then an empty properties
	 * object is returned.
	 * 
	 * @return Properties the feature's build.properties
	 */
	protected Properties getBuildProperties(String featureLocation) throws CoreException {
		return readProperties(featureLocation, PROPERTIES_FILE, director.isIgnoreMissingPropertiesFile() ? IStatus.OK : IStatus.WARNING);
	}

	/**
	 * Add the <code>children</code> target to the given Ant script.
	 * Delegates some target call to all-template only if the property
	 * includeChildren is set.
	 */
	private void generateChildrenTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_CHILDREN, null, PROPERTY_INCLUDE_CHILDREN, null, null);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, null);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>build.jars</code> target to the given Ant script.
	 */
	private void generateBuildJarsTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_BUILD_JARS, TARGET_INIT, null, null, NLS.bind(Messages.build_feature_buildJars, feature.getId()));
		Map<String, String> params = new HashMap<>(1);
		params.put(PROPERTY_TARGET, TARGET_BUILD_JARS);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printTargetEnd();
		script.println();
		script.printTargetDeclaration(TARGET_BUILD_SOURCES, TARGET_INIT, null, null, null);
		params.clear();
		params.put(PROPERTY_TARGET, TARGET_BUILD_SOURCES);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printTargetEnd();
	}

	/**
	 * Add the <code>refresh</code> target to the given Ant script.
	 */
	private void generateRefreshTarget() {
		script.println();
		script.printTargetDeclaration(TARGET_REFRESH, TARGET_INIT, PROPERTY_ECLIPSE_RUNNING, null, NLS.bind(Messages.build_feature_refresh, feature.getId()));
		script.printConvertPathTask(IPath.fromOSString(featureRootLocation).removeLastSegments(0).toOSString().replace('\\', '/'), PROPERTY_RESOURCE_PATH, false);
		script.printRefreshLocalTask(Utils.getPropertyFormat(PROPERTY_RESOURCE_PATH), "infinite"); //$NON-NLS-1$
		Map<String, String> params = new HashMap<>(2);
		params.put(PROPERTY_TARGET, TARGET_REFRESH);
		script.printAntCallTask(TARGET_ALL_CHILDREN, true, params);
		script.printTargetEnd();
	}

	public void setGenerateProductFiles(boolean generateProductFiles) {
		this.generateProductFiles = generateProductFiles;
	}
}
