/*******************************************************************************
 * Copyright (c) 2007, 2018 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 Corporation - initial API and implementation
 *     Genuitec, LLC
 *		EclipseSource - ongoing development
 *******************************************************************************/
package org.eclipse.equinox.p2.metadata;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.internal.p2.metadata.Copyright;
import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnit;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnitFragment;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnitPatch;
import org.eclipse.equinox.internal.p2.metadata.License;
import org.eclipse.equinox.internal.p2.metadata.ProvidedCapability;
import org.eclipse.equinox.internal.p2.metadata.RequiredCapability;
import org.eclipse.equinox.internal.p2.metadata.RequiredPropertiesMatch;
import org.eclipse.equinox.internal.p2.metadata.Requirement;
import org.eclipse.equinox.internal.p2.metadata.RequirementChange;
import org.eclipse.equinox.internal.p2.metadata.ResolvedInstallableUnit;
import org.eclipse.equinox.internal.p2.metadata.TouchpointData;
import org.eclipse.equinox.internal.p2.metadata.TouchpointInstruction;
import org.eclipse.equinox.internal.p2.metadata.TouchpointType;
import org.eclipse.equinox.internal.p2.metadata.UpdateDescriptor;
import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil;
import org.eclipse.equinox.p2.metadata.expression.IFilterExpression;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;

/**
 * A factory class for instantiating various p2 metadata objects.
 * 
 * @since 2.0
 */
public final class MetadataFactory {
	/**
	 * A description containing information about an installable unit. Once created,
	 * installable units are immutable. This description class allows a client to
	 * build up the state for an installable unit incrementally, and then finally
	 * produce the resulting immutable unit.
	 */
	public static class InstallableUnitDescription {
		InstallableUnit unit;

		/**
		 * A property key (value <code>"org.eclipse.equinox.p2.type.patch"</code>) for a
		 * boolean property indicating that an installable unit is a group.
		 * 
		 */
		public static final String PROP_TYPE_GROUP = "org.eclipse.equinox.p2.type.group"; //$NON-NLS-1$

		/**
		 * A property key (value <code>"org.eclipse.equinox.p2.type.patch"</code>) for a
		 * boolean property indicating that an installable unit is a patch.
		 * 
		 */
		public static final String PROP_TYPE_PATCH = "org.eclipse.equinox.p2.type.patch"; //$NON-NLS-1$

		/**
		 * A property key (value <code>"org.eclipse.equinox.p2.type.fragment"</code>)
		 * for a boolean property indicating that an installable unit is a fragment.
		 * 
		 */
		public static final String PROP_TYPE_FRAGMENT = "org.eclipse.equinox.p2.type.fragment"; //$NON-NLS-1$

		/**
		 * A property key (value <code>"org.eclipse.equinox.p2.type.category"</code>)
		 * for a boolean property indicating that an installable unit is a category.
		 * 
		 */
		public static final String PROP_TYPE_CATEGORY = "org.eclipse.equinox.p2.type.category"; //$NON-NLS-1$

		/**
		 * A property key (value <code>"org.eclipse.equinox.p2.type.product"</code>) for
		 * a boolean property indicating that an installable unit is the root IU of a
		 * product.
		 * 
		 * @since 2.2
		 */
		public static final String PROP_TYPE_PRODUCT = "org.eclipse.equinox.p2.type.product"; //$NON-NLS-1$

		public InstallableUnitDescription() {
			super();
		}

		/**
		 * Add the specified capabilities to the installable unit.
		 * 
		 * @param additional the capabilities to add.
		 */
		public void addProvidedCapabilities(Collection<IProvidedCapability> additional) {
			if (additional == null || additional.size() == 0)
				return;
			Collection<IProvidedCapability> current = unit().getProvidedCapabilities();
			ArrayList<IProvidedCapability> all = new ArrayList<>(additional.size() + current.size());
			all.addAll(current);
			all.addAll(additional);
			unit().setCapabilities(all.toArray(new IProvidedCapability[all.size()]));
		}

		/** @deprecated Use addRequirements(additional) instead */
		@Deprecated
		public void addRequiredCapabilities(Collection<IRequirement> additional) {
			addRequirements(additional);
		}

		/**
		 * Add the specified requirements to the installable unit.
		 * 
		 * @param additional the requirements to add
		 */
		public void addRequirements(Collection<IRequirement> additional) {
			if (additional == null || additional.size() == 0)
				return;
			List<IRequirement> current = unit().getRequirements();
			ArrayList<IRequirement> all = new ArrayList<>(additional.size() + current.size());
			all.addAll(current);
			all.addAll(additional);
			unit().setRequiredCapabilities(all.toArray(new IRequirement[all.size()]));
		}

		public void addTouchpointData(ITouchpointData data) {
			Assert.isNotNull(data);
			unit().addTouchpointData(data);
		}

		/**
		 * Returns the id of the installable unit.
		 */
		public String getId() {
			return unit().getId();
		}

		/**
		 * Return a collection of all the capabilities specified on this installable
		 * unit.
		 */
		public Collection<IProvidedCapability> getProvidedCapabilities() {
			return unit().getProvidedCapabilities();
		}

		/** @deprecated Use getRequirements() instead */
		@Deprecated
		public List<IRequirement> getRequiredCapabilities() {
			return getRequirements();
		}

		/**
		 * Return a collection of the requirements specified on this installable unit.
		 */
		public List<IRequirement> getRequirements() {
			return unit().getRequirements();
		}

		/** @deprecated Use getMetaRequirements() instead */
		@Deprecated
		public Collection<IRequirement> getMetaRequiredCapabilities() {
			return getMetaRequirements();
		}

		/**
		 * Return a collection of the meta requirements specified on this installable
		 * unit.
		 */
		public Collection<IRequirement> getMetaRequirements() {
			return unit().getMetaRequirements();
		}

		/**
		 * Returns the current touchpoint data on this installable unit description. The
		 * touchpoint data may change if further data is added to the description.
		 * 
		 * @return The current touchpoint data on this description
		 */
		public Collection<ITouchpointData> getTouchpointData() {
			return unit().getTouchpointData();

		}

		/**
		 * Return the versiono on this installable unit description.
		 */
		public Version getVersion() {
			return unit().getVersion();
		}

		/**
		 * Returns the UpdateDescriptor for this IU
		 * 
		 * @since 2.1
		 */
		public IUpdateDescriptor getUpdateDescriptor() {
			return unit().getUpdateDescriptor();
		}

		/**
		 * Set the artifact keys for the installable unit. Previous values will be
		 * overwritten.
		 * 
		 * @param value the artifacts to the used.
		 */
		public void setArtifacts(IArtifactKey[] value) {
			unit().setArtifacts(value);
		}

		/**
		 * Set the capabilities for the installable unit. Previous values will be
		 * overwritten.
		 * 
		 * @param exportedCapabilities the capabilities to be used.
		 */
		public void setCapabilities(IProvidedCapability[] exportedCapabilities) {
			unit().setCapabilities(exportedCapabilities);
		}

		/**
		 * Set the copyright for the installable unit. Previous values will be
		 * overwritten.
		 * 
		 * @param copyright the copyright to be used.
		 */
		public void setCopyright(ICopyright copyright) {
			unit().setCopyright(copyright);
		}

		public void setFilter(IMatchExpression<IInstallableUnit> filter) {
			unit().setFilter(filter);
		}

		public void setFilter(String filter) {
			unit().setFilter(filter);
		}

		/**
		 * Set the id of the installable unit.
		 */
		public void setId(String id) {
			unit().setId(id);
		}

		/**
		 * Set the licenses for the installable unit. Previous values will be
		 * overwritten.
		 */
		public void setLicenses(ILicense[] licenses) {
			unit().setLicenses(licenses);
		}

		/**
		 * Set a property with a specified value for this installable unit.
		 * 
		 * @param key   key with which the specified value is to be associated
		 * @param value value to be associated with the specified key
		 */
		public void setProperty(String key, String value) {
			unit().setProperty(key, value);
		}

		/** @deprecated Use setRequirements(requirements) instead */
		@Deprecated
		public void setRequiredCapabilities(IRequirement[] requirements) {
			setRequirements(requirements);
		}

		/**
		 * Set the requirements for the installable unit. Previous values will be
		 * overwritten.
		 * 
		 * @param requirements the requirements to be used.
		 */
		public void setRequirements(IRequirement[] requirements) {
			unit().setRequiredCapabilities(requirements);
		}

		/** @deprecated Use setMetaRequirements(requirements) instead */
		@Deprecated
		public void setMetaRequiredCapabilities(IRequirement[] metaRequirements) {
			setMetaRequirements(metaRequirements);
		}

		/**
		 * Set the meta requirements for the installable unit. Previous values will be
		 * overwritten.
		 * 
		 * @param metaRequirements the meta requirements to be used.
		 */
		public void setMetaRequirements(IRequirement[] metaRequirements) {
			unit().setMetaRequiredCapabilities(metaRequirements);
		}

		/**
		 * Change the singleton status of the installable unit.
		 */
		public void setSingleton(boolean singleton) {
			unit().setSingleton(singleton);
		}

		/**
		 * Set the touchpoint type for the installable unit.
		 */
		public void setTouchpointType(ITouchpointType type) {
			unit().setTouchpointType(type);
		}

		/**
		 * Set the update descriptor for the installable unit.
		 */
		public void setUpdateDescriptor(IUpdateDescriptor updateInfo) {
			unit().setUpdateDescriptor(updateInfo);
		}

		/**
		 * Set the version of this installable unit.
		 * 
		 * @param newVersion version to be set on the installable unit.
		 */
		public void setVersion(Version newVersion) {
			unit().setVersion(newVersion);
		}

		InstallableUnit unit() {
			if (unit == null) {
				unit = new InstallableUnit();
				unit.setArtifacts(new IArtifactKey[0]);
			}
			return unit;
		}

		IInstallableUnit unitCreate() {
			IInstallableUnit result = unit();
			this.unit = null;
			return result;
		}
	}

	/**
	 * A description containing information about an installable unit fragment. Once
	 * created, installable units are immutable. This description class allows a
	 * client to build up the state for an installable unit fragment incrementally,
	 * and then finally produce the resulting immutable unit.
	 */
	public static class InstallableUnitFragmentDescription extends InstallableUnitDescription {
		public InstallableUnitFragmentDescription() {
			super();
			setProperty(InstallableUnitDescription.PROP_TYPE_FRAGMENT, Boolean.TRUE.toString());
		}

		/**
		 * Specify the requirements identifying the host to which the installable unit
		 * fragment should be attached to.
		 */
		public void setHost(IRequirement... hostRequirement) {
			((InstallableUnitFragment) unit()).setHost(Arrays.asList(hostRequirement));
		}

		@Override
		InstallableUnit unit() {
			if (unit == null)
				unit = new InstallableUnitFragment();
			return unit;
		}
	}

	/**
	 * A description containing information about an installable unit patch. Once
	 * created, installable units are immutable. This description class allows a
	 * client to build up the state for an installable unit patch incrementally, and
	 * then finally produce the resulting immutable unit.
	 */
	public static class InstallableUnitPatchDescription extends InstallableUnitDescription {

		public InstallableUnitPatchDescription() {
			super();
			setProperty(InstallableUnitDescription.PROP_TYPE_PATCH, Boolean.TRUE.toString());
		}

		/**
		 * Set the applicability scope for the installable unit patch.
		 */
		public void setApplicabilityScope(IRequirement[][] applyTo) {
			if (applyTo == null)
				throw new IllegalArgumentException("A patch scope can not be null"); //$NON-NLS-1$
			((InstallableUnitPatch) unit()).setApplicabilityScope(applyTo);
		}

		/**
		 * Set the lifecycle change for the installable unit patch.
		 */
		public void setLifeCycle(IRequirement lifeCycle) {
			((InstallableUnitPatch) unit()).setLifeCycle(lifeCycle);
		}

		/**
		 * Set the requirement change for the installable unit patch.
		 */
		public void setRequirementChanges(IRequirementChange[] changes) {
			((InstallableUnitPatch) unit()).setRequirementsChange(changes);
		}

		@Override
		InstallableUnit unit() {
			if (unit == null) {
				unit = new InstallableUnitPatch();
				((InstallableUnitPatch) unit()).setApplicabilityScope(new IRequirement[0][0]);
			}
			return unit;
		}
	}

	/**
	 * Singleton touchpoint data for a touchpoint with no instructions.
	 */
	private static final ITouchpointData EMPTY_TOUCHPOINT_DATA = new TouchpointData(Collections.emptyMap());

	private static ITouchpointType[] typeCache = new ITouchpointType[5];

	private static int typeCacheOffset;

	/**
	 * Returns an {@link IInstallableUnit} based on the given description. Once the
	 * installable unit has been created, the information is discarded from the
	 * description object.
	 * 
	 * @param description The description of the unit to create
	 * @return The created installable unit
	 */
	public static IInstallableUnit createInstallableUnit(InstallableUnitDescription description) {
		Assert.isNotNull(description);
		return description.unitCreate();
	}

	/**
	 * Returns an {@link IInstallableUnitFragment} based on the given description.
	 * Once the fragment has been created, the information is discarded from the
	 * description object.
	 * 
	 * @param description The description of the unit to create
	 * @return The created installable unit fragment
	 */
	public static IInstallableUnitFragment createInstallableUnitFragment(
			InstallableUnitFragmentDescription description) {
		Assert.isNotNull(description);
		return (IInstallableUnitFragment) description.unitCreate();
	}

	/**
	 * Returns an {@link IInstallableUnitPatch} based on the given description. Once
	 * the patch installable unit has been created, the information is discarded
	 * from the description object.
	 * 
	 * @param description The description of the unit to create
	 * @return The created installable unit patch
	 */
	public static IInstallableUnitPatch createInstallableUnitPatch(InstallableUnitPatchDescription description) {
		Assert.isNotNull(description);
		return (IInstallableUnitPatch) description.unitCreate();
	}

	/**
	 * Returns a {@link IProvidedCapability} with the given values.
	 * 
	 * @param namespace The capability namespace
	 * @param name      The capability name
	 * @param version   The capability version
	 */
	public static IProvidedCapability createProvidedCapability(String namespace, String name, Version version) {
		return new ProvidedCapability(namespace, name, version);
	}

	/**
	 * Returns a {@link IProvidedCapability} with the given values.
	 * 
	 * @param namespace  The capability namespace
	 * @param properties The description of the capability
	 * @since 2.4
	 */
	public static IProvidedCapability createProvidedCapability(String namespace, Map<String, Object> properties) {
		return new ProvidedCapability(namespace, properties);
	}

	/**
	 * Create and return a new requirement ({@link IRequirement}) with the specified
	 * values.
	 * 
	 * @param namespace the namespace for the requirement. Must not be
	 *                  <code>null</code>.
	 * @param name      the name for the requirement. Must not be <code>null</code>.
	 * @param range     the version range. A value of <code>null</code> is
	 *                  equivalent to {@link VersionRange#emptyRange} and matches
	 *                  all versions.
	 * @param filter    The filter used to evaluate whether this capability is
	 *                  applicable in the current environment, or <code>null</code>
	 *                  to indicate this capability is always applicable
	 * @param optional  <code>true</code> if this requirement is optional, and
	 *                  <code>false</code> otherwise.
	 * @param multiple  <code>true</code> if this requirement can be satisfied by
	 *                  multiple provided capabilities, or <code>false</code> if it
	 *                  requires exactly one match
	 * @param greedy    <code>true</code> if the requirement should be considered
	 *                  greedy and <code>false</code> otherwise
	 * @return the requirement
	 */
	public static IRequirement createRequirement(String namespace, String name, VersionRange range, String filter,
			boolean optional, boolean multiple, boolean greedy) {
		return new RequiredCapability(namespace, name, range, InstallableUnit.parseFilter(filter), optional ? 0 : 1,
				multiple ? Integer.MAX_VALUE : 1, greedy, null);
	}

	/**
	 * Returns a {@link IRequirement} with the given values.
	 * 
	 * @param namespace The capability namespace
	 * @param name      The required capability name
	 * @param range     The range of versions that are required, or
	 *                  <code>null</code> to indicate that any version will do.
	 * @param filter    The filter used to evaluate whether this capability is
	 *                  applicable in the current environment, or <code>null</code>
	 *                  to indicate this capability is always applicable
	 * @param optional  <code>true</code> if this required capability is optional,
	 *                  and <code>false</code> otherwise.
	 * @param multiple  <code>true</code> if this capability can be satisfied by
	 *                  multiple provided capabilities, or <code>false</code> if it
	 *                  requires exactly one match
	 * @return the requirement
	 */
	public static IRequirement createRequirement(String namespace, String name, VersionRange range,
			IMatchExpression<IInstallableUnit> filter, boolean optional, boolean multiple) {
		return new RequiredCapability(namespace, name, range, filter, optional ? 0 : 1,
				multiple ? Integer.MAX_VALUE : 1, true, null);
	}

	/**
	 * Create and return a new requirement ({@link IRequirement}) with the specified
	 * values.
	 * 
	 * @param namespace the namespace for the requirement. Must not be
	 *                  <code>null</code>.
	 * @param name      the name for the requirement. Must not be <code>null</code>.
	 * @param range     the version range. A value of <code>null</code> is
	 *                  equivalent to {@link VersionRange#emptyRange} and matches
	 *                  all versions.
	 * @param filter    The filter used to evaluate whether this capability is
	 *                  applicable in the current environment, or <code>null</code>
	 *                  to indicate this capability is always applicable
	 * @param minCard   minimum cardinality
	 * @param maxCard   maximum cardinality
	 * @param greedy    <code>true</code> if the requirement should be considered
	 *                  greedy and <code>false</code> otherwise
	 * @return the requirement
	 */
	public static IRequirement createRequirement(String namespace, String name, VersionRange range,
			IMatchExpression<IInstallableUnit> filter, int minCard, int maxCard, boolean greedy) {
		return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, null);
	}

	/**
	 * Create and return a new requirement ({@link IRequirement}) with the specified
	 * values.
	 * 
	 * @param namespace   the namespace for the requirement. Must not be
	 *                    <code>null</code>.
	 * @param name        the name for the requirement. Must not be
	 *                    <code>null</code>.
	 * @param range       the version range. A value of <code>null</code> is
	 *                    equivalent to {@link VersionRange#emptyRange} and matches
	 *                    all versions.
	 * @param filter      The filter used to evaluate whether this capability is
	 *                    applicable in the current environment, or
	 *                    <code>null</code> to indicate this capability is always
	 *                    applicable
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @param description a <code>String</code> description of the requirement, or
	 *                    <code>null</code>
	 * @return the requirement
	 */
	public static IRequirement createRequirement(String namespace, String name, VersionRange range,
			IMatchExpression<IInstallableUnit> filter, int minCard, int maxCard, boolean greedy, String description) {
		return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, description);
	}

	/**
	 * 
	 * @param namespace   the namespace for the requirement. Must not be
	 *                    <code>null</code>.
	 * @param propsFilter filter applied on
	 *                    {@link IProvidedCapability#getProperties()} of every
	 *                    {@link IInstallableUnit#getProvidedCapabilities()}
	 * @param envFilter   matcher over {@link IInstallableUnit#getProperties()}
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @return the requirement
	 * @since 2.4
	 */
	public static IRequirement createRequirement(String namespace, String propsFilter,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy) {
		IFilterExpression attrFilterExpr = ExpressionUtil.parseLDAP(propsFilter);
		return new RequiredPropertiesMatch(namespace, attrFilterExpr, envFilter, minCard, maxCard, greedy, null);
	}

	/**
	 * 
	 * @param namespace   the namespace for the requirement. Must not be
	 *                    <code>null</code>.
	 * @param propsFilter filter applied on
	 *                    {@link IProvidedCapability#getProperties()} of every
	 *                    {@link IInstallableUnit#getProvidedCapabilities()}
	 * @param envFilter   matcher over {@link IInstallableUnit#getProperties()}
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @return the requirement
	 * @since 2.5
	 */
	public static IRequirement createRequirement(String namespace, String propsFilter,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy,
			String description) {
		IFilterExpression attrFilterExpr = ExpressionUtil.parseLDAP(propsFilter);
		return new RequiredPropertiesMatch(namespace, attrFilterExpr, envFilter, minCard, maxCard, greedy, description);
	}

	/**
	 * 
	 * @param namespace   the namespace for the requirement. Must not be
	 *                    <code>null</code>.
	 * @param propsFilter filter applied on
	 *                    {@link IProvidedCapability#getProperties()} of every
	 *                    {@link IInstallableUnit#getProvidedCapabilities()}
	 * @param envFilter   matcher over {@link IInstallableUnit#getProperties()}
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @param description a <code>String</code> description of the requirement, or
	 *                    <code>null</code>
	 * @return the requirement
	 * @since 2.4
	 */
	public static IRequirement createRequirement(String namespace, IFilterExpression propsFilter,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy,
			String description) {
		return new RequiredPropertiesMatch(namespace, propsFilter, envFilter, minCard, maxCard, greedy, description);
	}

	/**
	 * Create and return a new requirement ({@link IRequirement}) with the specified
	 * values.
	 * 
	 * @param requirement the match expression
	 * @param envFilter   The filter used to evaluate whether this capability is
	 *                    applicable in the current environment, or
	 *                    <code>null</code> to indicate this capability is always
	 *                    applicable
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @return the requirement
	 */
	public static IRequirement createRequirement(IMatchExpression<IInstallableUnit> requirement,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy) {
		return createRequirementInternal(requirement, envFilter, minCard, maxCard, greedy, null);
	}

	/**
	 * Create and return a new requirement ({@link IRequirement}) with the specified
	 * values.
	 * 
	 * @param requirement the match expression
	 * @param envFilter   the filter, or <code>null</code>
	 * @param minCard     minimum cardinality
	 * @param maxCard     maximum cardinality
	 * @param greedy      <code>true</code> if the requirement should be considered
	 *                    greedy and <code>false</code> otherwise
	 * @param description a <code>String</code> description of the requirement, or
	 *                    <code>null</code>
	 * @return the requirement
	 */
	public static IRequirement createRequirement(IMatchExpression<IInstallableUnit> requirement,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy,
			String description) {
		return createRequirementInternal(requirement, envFilter, minCard, maxCard, greedy, description);
	}

	/**
	 * Returns a new requirement change.
	 * 
	 * @param applyOn  The source of the requirement change - the kind of
	 *                 requirement to apply the change to
	 * @param newValue The result of the requirement change - the requirement to
	 *                 replace the source requirement with
	 * @return a requirement change
	 */
	public static IRequirementChange createRequirementChange(IRequirement applyOn, IRequirement newValue) {
		if ((applyOn == null || applyOn instanceof IRequiredCapability)
				&& (newValue == null || newValue instanceof IRequiredCapability))
			return new RequirementChange((IRequiredCapability) applyOn, (IRequiredCapability) newValue);
		throw new IllegalArgumentException();
	}

	/**
	 * Returns a new {@link ICopyright}.
	 * 
	 * @param location the location of a document containing the copyright notice,
	 *                 or <code>null</code>
	 * @param body     the copyright body, cannot be <code>null</code>
	 * @throws IllegalArgumentException when the <code>body</code> is
	 *                                  <code>null</code>
	 */
	public static ICopyright createCopyright(URI location, String body) {
		return new Copyright(location, body);
	}

	/**
	 * Return a new {@link ILicense} The body should contain either the full text of
	 * the license or an summary for a license fully specified in the given
	 * location.
	 * 
	 * @param location the location of a document containing the full license, or
	 *                 <code>null</code>
	 * @param body     the license body, cannot be <code>null</code>
	 * @throws IllegalArgumentException when the <code>body</code> is
	 *                                  <code>null</code>
	 */
	public static ILicense createLicense(URI location, String body) {
		return new License(location, body, null);
	}

	/**
	 * Returns an {@link IInstallableUnit} that represents the given unit bound to
	 * the given fragments.
	 * 
	 * @see IInstallableUnit#isResolved()
	 * @param unit      The unit to be bound
	 * @param fragments The fragments to be bound
	 * @return A resolved installable unit
	 */
	public static IInstallableUnit createResolvedInstallableUnit(IInstallableUnit unit,
			IInstallableUnitFragment[] fragments) {
		if (unit.isResolved())
			return unit;
		Assert.isNotNull(unit);
		Assert.isNotNull(fragments);
		return new ResolvedInstallableUnit(unit, fragments);

	}

	/**
	 * Returns an instance of {@link ITouchpointData} with the given instructions.
	 * 
	 * @param instructions The instructions for the touchpoint data.
	 * @return The created touchpoint data
	 */
	public static ITouchpointData createTouchpointData(Map<String, ? extends Object> instructions) {
		Assert.isNotNull(instructions);
		// copy the map to protect against subsequent change by caller
		if (instructions.isEmpty())
			return EMPTY_TOUCHPOINT_DATA;

		Map<String, ITouchpointInstruction> result = new LinkedHashMap<>(instructions.size());

		for (Map.Entry<String, ? extends Object> entry : instructions.entrySet()) {
			Object value = entry.getValue();
			ITouchpointInstruction instruction;
			if (value == null || value instanceof String)
				instruction = createTouchpointInstruction((String) value, null);
			else
				instruction = (ITouchpointInstruction) value;
			result.put(entry.getKey(), instruction);
		}
		return new TouchpointData(result);
	}

	/**
	 * Merge the given touchpoint instructions with a pre-existing touchpoint data
	 * 
	 * @param initial              - the initial ITouchpointData
	 * @param incomingInstructions - Map of ITouchpointInstructions to merge into
	 *                             the initial touchpoint data
	 * @return the merged ITouchpointData
	 */
	public static ITouchpointData mergeTouchpointData(ITouchpointData initial,
			Map<String, ITouchpointInstruction> incomingInstructions) {
		if (incomingInstructions == null || incomingInstructions.size() == 0)
			return initial;

		Map<String, ITouchpointInstruction> resultInstructions = new HashMap<>(initial.getInstructions());
		for (String key : incomingInstructions.keySet()) {
			ITouchpointInstruction instruction = incomingInstructions.get(key);
			ITouchpointInstruction existingInstruction = resultInstructions.get(key);

			if (existingInstruction != null) {
				String body = existingInstruction.getBody();
				if (body == null || body.length() == 0)
					body = instruction.getBody();
				else if (instruction.getBody() != null) {
					if (!body.endsWith(";")) //$NON-NLS-1$
						body += ';';
					body += instruction.getBody();
				}

				String importAttribute = existingInstruction.getImportAttribute();
				if (importAttribute == null || importAttribute.length() == 0)
					importAttribute = instruction.getImportAttribute();
				else if (instruction.getImportAttribute() != null) {
					if (!importAttribute.endsWith(",")) //$NON-NLS-1$
						importAttribute += ',';
					importAttribute += instruction.getBody();
				}
				instruction = createTouchpointInstruction(body, importAttribute);
			}
			resultInstructions.put(key, instruction);
		}
		return createTouchpointData(resultInstructions);
	}

	public static ITouchpointInstruction createTouchpointInstruction(String body, String importAttribute) {
		return new TouchpointInstruction(body, importAttribute);
	}

	/**
	 * Returns a {@link TouchpointType} with the given id and version.
	 * 
	 * @param id      The touchpoint id
	 * @param version The touchpoint version
	 * @return A touchpoint type instance with the given id and version
	 */
	public static ITouchpointType createTouchpointType(String id, Version version) {
		Assert.isNotNull(id);
		Assert.isNotNull(version);

		if (id.equals(ITouchpointType.NONE.getId()) && version.equals(ITouchpointType.NONE.getVersion()))
			return ITouchpointType.NONE;

		synchronized (typeCache) {
			ITouchpointType result = getCachedTouchpointType(id, version);
			if (result != null)
				return result;
			result = new TouchpointType(id, version);
			putCachedTouchpointType(result);
			return result;
		}
	}

	/**
	 * 
	 * @param descriptors The IUs that the descriptor provides updates for.
	 * @param severity    The update severity (either
	 *                    {@link IUpdateDescriptor#NORMAL} or
	 *                    {@link IUpdateDescriptor#HIGH})
	 * @param description A description of the update
	 * @param location    a {@link URI} specifying the location or <code>null</code>
	 * @return A new update descriptor
	 */
	public static IUpdateDescriptor createUpdateDescriptor(Collection<IMatchExpression<IInstallableUnit>> descriptors,
			int severity, String description, URI location) {
		return new UpdateDescriptor(descriptors, severity, description, location);
	}

	/**
	 * Creates a new update descriptor object.
	 * 
	 * @param id          The id of the installable unit that the descriptor
	 *                    provides updates for
	 * @param range       The version range that the descriptor provides updates for
	 * @param severity    The update severity (either
	 *                    {@link IUpdateDescriptor#NORMAL} or
	 *                    {@link IUpdateDescriptor#HIGH})
	 * @param description A description of the update
	 * @return A new update descriptor
	 */
	public static IUpdateDescriptor createUpdateDescriptor(String id, VersionRange range, int severity,
			String description) {
		return createUpdateDescriptor(id, range, severity, description, null);
	}

	/**
	 * Create and return a new update descriptor {@link IUpdateDescriptor} with the
	 * specified values.
	 * 
	 * @param id          the identifiter for the update. Must not be
	 *                    <code>null</code>.
	 * @param range       the version range. A <code>null</code> range is equivalent
	 *                    to {@link VersionRange#emptyRange} and matches all
	 *                    versions.
	 * @param severity    the severity
	 * @param description a <code>String</code> description or <code>null</code>
	 * @param location    a {@link URI} specifying the location or <code>null</code>
	 * @return the update descriptor
	 */
	public static IUpdateDescriptor createUpdateDescriptor(String id, VersionRange range, int severity,
			String description, URI location) {
		Collection<IMatchExpression<IInstallableUnit>> descriptors = new ArrayList<>(1);
		descriptors.add(RequiredCapability.createMatchExpressionFromRange(IInstallableUnit.NAMESPACE_IU_ID, id, range));
		return createUpdateDescriptor(descriptors, severity, description, location);
	}

	private static IRequirement createRequirementInternal(IMatchExpression<IInstallableUnit> requirement,
			IMatchExpression<IInstallableUnit> envFilter, int minCard, int maxCard, boolean greedy,
			String description) {
		// IRequiredCapability is simply a requirement with a match expression derived
		// from a (namespace, name, version) tripet.
		// However the xml format also requires that maxCard > 1 or it is serialized in
		// the generic format.
		// When parsing back from xml try to convert to an IRequiredCapability to retain
		// the representation prior to serialization
		if (RequiredCapability.isVersionRangeRequirement(requirement)) {
			String namespace = RequiredCapability.extractNamespace(requirement);
			String name = RequiredCapability.extractName(requirement);
			VersionRange range = RequiredCapability.extractRange(requirement);
			return new RequiredCapability(namespace, name, range, envFilter, minCard, maxCard, greedy, description);
		}

		if (RequiredPropertiesMatch.isPropertiesMatchRequirement(requirement)) {
			String namespace = RequiredPropertiesMatch.extractNamespace(requirement);
			IFilterExpression attrMatch = RequiredPropertiesMatch.extractPropertiesMatch(requirement);
			return new RequiredPropertiesMatch(namespace, attrMatch, envFilter, minCard, maxCard, greedy, description);
		}

		return new Requirement(requirement, envFilter, minCard, maxCard, greedy, description);
	}

	private static ITouchpointType getCachedTouchpointType(String id, Version version) {
		for (ITouchpointType typeCache1 : typeCache) {
			if (typeCache1 != null && typeCache1.getId().equals(id) && typeCache1.getVersion().equals(version)) {
				return typeCache1;
			}
		}
		return null;
	}

	private static void putCachedTouchpointType(ITouchpointType result) {
		// simple rotating buffer
		typeCache[typeCacheOffset] = result;
		typeCacheOffset = (typeCacheOffset + 1) % typeCache.length;
	}
}
