/*******************************************************************************
 * Copyright (c) 2007, 2022 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 - added license support
 *		EclipseSource - ongoing development
 *		Christoph Läubrich - Issue #20 - XMLParser should not require a bundle context but a Parser in the constructor
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.repository.io;

import static java.util.stream.Collectors.toList;

import java.net.URI;
import java.util.*;
import java.util.Map.Entry;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnit;
import org.eclipse.equinox.internal.p2.persistence.XMLParser;
import org.eclipse.equinox.p2.metadata.*;
import org.eclipse.equinox.p2.metadata.MetadataFactory.*;
import org.eclipse.equinox.p2.metadata.expression.*;
import org.eclipse.equinox.p2.repository.IRepositoryReference;
import org.eclipse.equinox.p2.repository.spi.RepositoryReference;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;

public abstract class MetadataParser extends XMLParser implements XMLConstants {
	static final ILicense[] NO_LICENSES = new ILicense[0];

	public MetadataParser(String bundleId) {
		super(bundleId);
	}

	public MetadataParser(SAXParserFactory factory, String bundleId) {
		super(factory, bundleId);
	}

	protected abstract class AbstractMetadataHandler extends AbstractHandler {

		public AbstractMetadataHandler(ContentHandler parentHandler, String elementHandled) {
			super(parentHandler, elementHandled);
		}

		int getOptionalSize(Attributes attributes, int dflt) {
			String sizeStr = parseOptionalAttribute(attributes, COLLECTION_SIZE_ATTRIBUTE);
			return sizeStr != null ? Integer.parseInt(sizeStr) : dflt;
		}
	}

	protected class RepositoryReferencesHandler extends AbstractMetadataHandler {
		private HashSet<IRepositoryReference> references;

		public RepositoryReferencesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, REPOSITORY_REFERENCES_ELEMENT);
			references = new HashSet<>(getOptionalSize(attributes, 4));
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(REPOSITORY_REFERENCE_ELEMENT)) {
				new RepositoryReferenceHandler(this, attributes, references);
			} else {
				invalidElement(name, attributes);
			}
		}

		public IRepositoryReference[] getReferences() {
			return references.toArray(new IRepositoryReference[references.size()]);
		}
	}

	protected class RepositoryReferenceHandler extends AbstractHandler {

		private final String[] required = new String[] {TYPE_ATTRIBUTE, OPTIONS_ATTRIBUTE};

		public RepositoryReferenceHandler(AbstractHandler parentHandler, Attributes attributes, Set<IRepositoryReference> references) {
			super(parentHandler, REPOSITORY_REFERENCE_ELEMENT);
			String[] values = parseRequiredAttributes(attributes, required);
			String name = parseOptionalAttribute(attributes, NAME_ATTRIBUTE);
			int type = checkInteger(elementHandled, TYPE_ATTRIBUTE, values[0]);
			int options = checkInteger(elementHandled, OPTIONS_ATTRIBUTE, values[1]);
			URI location = parseURIAttribute(attributes, true);
			if (location != null)
				references.add(new RepositoryReference(location, name, type, options));
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			invalidElement(name, attributes);
		}
	}

	protected class InstallableUnitsHandler extends AbstractMetadataHandler {
		private ArrayList<InstallableUnitDescription> units;

		public InstallableUnitsHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, INSTALLABLE_UNITS_ELEMENT);
			units = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		public IInstallableUnit[] getUnits() {
			int size = units.size();
			IInstallableUnit[] result = new IInstallableUnit[size];
			int i = 0;
			for (InstallableUnitDescription desc : units)
				result[i++] = MetadataFactory.createInstallableUnit(desc);
			return result;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(INSTALLABLE_UNIT_ELEMENT)) {
				new InstallableUnitHandler(this, attributes, units);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class InstallableUnitHandler extends AbstractHandler {

		InstallableUnitDescription currentUnit = null;

		private PropertiesHandler propertiesHandler = null;
		private ProvidedCapabilitiesHandler providedCapabilitiesHandler = null;
		private RequirementsHandler requiredCapabilitiesHandler = null;
		private HostRequiredCapabilitiesHandler hostRequiredCapabilitiesHandler = null;
		private MetaRequiredCapabilitiesHandler metaRequiredCapabilitiesHandler = null;
		private TextHandler filterHandler = null;
		private ArtifactsHandler artifactsHandler = null;
		private TouchpointTypeHandler touchpointTypeHandler = null;
		private TouchpointDataHandler touchpointDataHandler = null;
		private UpdateDescriptorHandler updateDescriptorHandler = null;
		private LicensesHandler licensesHandler = null;
		private CopyrightHandler copyrightHandler = null;
		private RequirementsChangeHandler requirementChangesHandler = null;
		private ApplicabilityScopesHandler applicabilityScopeHandler = null;
		private LifeCycleHandler lifeCycleHandler;

		private String id;
		private Version version;
		private boolean singleton;

		private List<InstallableUnitDescription> units;

		public InstallableUnitHandler(AbstractHandler parentHandler, Attributes attributes, List<InstallableUnitDescription> units) {
			super(parentHandler, INSTALLABLE_UNIT_ELEMENT);
			String[] values = parseAttributes(attributes, REQUIRED_IU_ATTRIBUTES, OPTIONAL_IU_ATTRIBUTES);
			this.units = units;
			//skip entire IU if the id is missing
			if (values[0] == null)
				return;

			id = values[0];
			version = checkVersion(INSTALLABLE_UNIT_ELEMENT, VERSION_ATTRIBUTE, values[1]);
			singleton = checkBoolean(INSTALLABLE_UNIT_ELEMENT, SINGLETON_ATTRIBUTE, values[2], true).booleanValue();
		}

		public IInstallableUnit getInstallableUnit() {
			return MetadataFactory.createInstallableUnit(currentUnit);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			checkCancel();
			if (PROPERTIES_ELEMENT.equals(name)) {
				if (propertiesHandler == null) {
					propertiesHandler = new PropertiesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (PROVIDED_CAPABILITIES_ELEMENT.equals(name)) {
				if (providedCapabilitiesHandler == null) {
					providedCapabilitiesHandler = new ProvidedCapabilitiesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (REQUIREMENTS_ELEMENT.equals(name)) {
				if (requiredCapabilitiesHandler == null) {
					requiredCapabilitiesHandler = new RequirementsHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (HOST_REQUIREMENTS_ELEMENT.equals(name)) {
				if (hostRequiredCapabilitiesHandler == null) {
					hostRequiredCapabilitiesHandler = new HostRequiredCapabilitiesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (META_REQUIREMENTS_ELEMENT.equals(name)) {
				if (metaRequiredCapabilitiesHandler == null) {
					metaRequiredCapabilitiesHandler = new MetaRequiredCapabilitiesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (IU_FILTER_ELEMENT.equals(name)) {
				if (filterHandler == null) {
					filterHandler = new TextHandler(this, IU_FILTER_ELEMENT, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (ARTIFACT_KEYS_ELEMENT.equals(name)) {
				if (artifactsHandler == null) {
					artifactsHandler = new ArtifactsHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (TOUCHPOINT_TYPE_ELEMENT.equals(name)) {
				if (touchpointTypeHandler == null) {
					touchpointTypeHandler = new TouchpointTypeHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (TOUCHPOINT_DATA_ELEMENT.equals(name)) {
				if (touchpointDataHandler == null) {
					touchpointDataHandler = new TouchpointDataHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (UPDATE_DESCRIPTOR_ELEMENT.equals(name)) {
				if (updateDescriptorHandler == null)
					updateDescriptorHandler = new UpdateDescriptorHandler(this, attributes);
				else {
					duplicateElement(this, name, attributes);
				}
			} else if (LICENSES_ELEMENT.equals(name)) {
				if (licensesHandler == null) {
					licensesHandler = new LicensesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (REQUIREMENT_CHANGES.equals(name)) {
				if (requirementChangesHandler == null) {
					requirementChangesHandler = new RequirementsChangeHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (APPLICABILITY_SCOPE.equals(name)) {
				if (applicabilityScopeHandler == null) {
					applicabilityScopeHandler = new ApplicabilityScopesHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (LIFECYCLE.equals(name)) {
				if (lifeCycleHandler == null) {
					lifeCycleHandler = new LifeCycleHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else if (COPYRIGHT_ELEMENT.equals(name)) {
				if (copyrightHandler == null) {
					copyrightHandler = new CopyrightHandler(this, attributes);
				} else {
					duplicateElement(this, name, attributes);
				}
			} else {
				invalidElement(name, attributes);
			}
		}

		@Override
		protected void finished() {
			if (isValidXML()) {
				if (requirementChangesHandler != null) {
					currentUnit = new MetadataFactory.InstallableUnitPatchDescription();
					((InstallableUnitPatchDescription) currentUnit).setRequirementChanges(requirementChangesHandler.getRequirementChanges().toArray(new IRequirementChange[requirementChangesHandler.getRequirementChanges().size()]));
					if (applicabilityScopeHandler != null)
						((InstallableUnitPatchDescription) currentUnit).setApplicabilityScope(applicabilityScopeHandler.getScope());
					if (lifeCycleHandler != null)
						((InstallableUnitPatchDescription) currentUnit).setLifeCycle(lifeCycleHandler.getLifeCycleRequirement());
				} else if (hostRequiredCapabilitiesHandler == null || hostRequiredCapabilitiesHandler.getHostRequiredCapabilities().length == 0) {
					currentUnit = new InstallableUnitDescription();
				} else {
					currentUnit = new MetadataFactory.InstallableUnitFragmentDescription();
					((InstallableUnitFragmentDescription) currentUnit).setHost(hostRequiredCapabilitiesHandler.getHostRequiredCapabilities());
				}
				currentUnit.setId(id);
				currentUnit.setVersion(version);
				currentUnit.setSingleton(singleton);
				OrderedProperties properties = (propertiesHandler == null ? new OrderedProperties(0) : propertiesHandler.getProperties());
				String updateFrom = null;
				VersionRange updateRange = null;
				for (Entry<String, String> e : properties.entrySet()) {
					String key = e.getKey();
					String value = e.getValue();
					//Backward compatibility
					if (key.equals("equinox.p2.update.from")) { //$NON-NLS-1$
						updateFrom = value;
						continue;
					}
					if (key.equals("equinox.p2.update.range")) { //$NON-NLS-1$
						updateRange = VersionRange.create(value);
						continue;
					}
					//End of backward compatibility
					currentUnit.setProperty(key, value);
				}
				//Backward compatibility
				if (updateFrom != null && updateRange != null)
					currentUnit.setUpdateDescriptor(MetadataFactory.createUpdateDescriptor(updateFrom, updateRange, IUpdateDescriptor.NORMAL, null));
				//End of backward compatibility

				if (licensesHandler != null) {
					currentUnit.setLicenses(licensesHandler.getLicenses());
				}

				if (copyrightHandler != null) {
					ICopyright copyright = copyrightHandler.getCopyright();
					currentUnit.setCopyright(copyright);
				}

				IProvidedCapability[] providedCapabilities = (providedCapabilitiesHandler == null ? new IProvidedCapability[0] : providedCapabilitiesHandler.getProvidedCapabilities());
				currentUnit.setCapabilities(providedCapabilities);
				IRequirement[] requiredCapabilities = (requiredCapabilitiesHandler == null ? new IRequirement[0] : requiredCapabilitiesHandler.getRequirements());
				currentUnit.setRequirements(requiredCapabilities);
				IRequirement[] metaRequiredCapabilities = (metaRequiredCapabilitiesHandler == null ? new IRequirement[0] : metaRequiredCapabilitiesHandler.getMetaRequiredCapabilities());
				currentUnit.setMetaRequirements(metaRequiredCapabilities);
				if (filterHandler != null) {
					currentUnit.setFilter(filterHandler.getText());
				}
				IArtifactKey[] artifacts = (artifactsHandler == null ? new IArtifactKey[0] : artifactsHandler.getArtifactKeys());
				currentUnit.setArtifacts(artifacts);
				if (touchpointTypeHandler != null) {
					currentUnit.setTouchpointType(touchpointTypeHandler.getTouchpointType());
				} else {
					// TODO: create an error
				}
				ITouchpointData[] touchpointData = (touchpointDataHandler == null ? new ITouchpointData[0] : touchpointDataHandler.getTouchpointData());
				for (ITouchpointData touchpointData1 : touchpointData) {
					currentUnit.addTouchpointData(touchpointData1);
				}
				if (updateDescriptorHandler != null)
					currentUnit.setUpdateDescriptor(updateDescriptorHandler.getUpdateDescriptor());
				units.add(currentUnit);
			}
		}
	}

	protected class ApplicabilityScopesHandler extends AbstractMetadataHandler {
		private List<IRequirement[]> scopes;

		public ApplicabilityScopesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, APPLICABILITY_SCOPE);
			scopes = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (APPLY_ON.equals(name)) {
				new ApplicabilityScopeHandler(this, attributes, scopes);
			} else {
				duplicateElement(this, name, attributes);
			}
		}

		public IRequirement[][] getScope() {
			return scopes.toArray(new IRequirement[scopes.size()][]);
		}
	}

	protected class ApplicabilityScopeHandler extends AbstractHandler {
		private RequirementsHandler children;
		private List<IRequirement[]> scopes;

		public ApplicabilityScopeHandler(AbstractHandler parentHandler, Attributes attributes, List<IRequirement[]> scopes) {
			super(parentHandler, APPLY_ON);
			this.scopes = scopes;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (REQUIREMENTS_ELEMENT.equals(name)) {
				children = new RequirementsHandler(this, attributes);
			} else {
				duplicateElement(this, name, attributes);
			}
		}

		@Override
		protected void finished() {
			if (children != null) {
				scopes.add(children.getRequirements());
			}
		}
	}

	protected class RequirementsChangeHandler extends AbstractMetadataHandler {
		private List<IRequirementChange> requirementChanges;

		public RequirementsChangeHandler(InstallableUnitHandler parentHandler, Attributes attributes) {
			super(parentHandler, REQUIREMENT_CHANGES);
			requirementChanges = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(REQUIREMENT_CHANGE)) {
				new RequirementChangeHandler(this, attributes, requirementChanges);
			} else {
				invalidElement(name, attributes);
			}
		}

		public List<IRequirementChange> getRequirementChanges() {
			return requirementChanges;
		}
	}

	protected class RequirementChangeHandler extends AbstractHandler {
		private List<IRequirement> from;
		private List<IRequirement> to;
		private List<IRequirementChange> requirementChanges;

		public RequirementChangeHandler(AbstractHandler parentHandler, Attributes attributes, List<IRequirementChange> requirementChanges) {
			super(parentHandler, REQUIREMENT_CHANGE);
			from = new ArrayList<>(1);
			to = new ArrayList<>(1);
			this.requirementChanges = requirementChanges;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(REQUIREMENT_FROM)) {
				new RequirementChangeEltHandler(this, REQUIREMENT_FROM, attributes, from);
				return;
			}

			if (name.equals(REQUIREMENT_TO)) {
				new RequirementChangeEltHandler(this, REQUIREMENT_TO, attributes, to);
				return;
			}
			invalidElement(name, attributes);
		}

		@Override
		protected void finished() {
			requirementChanges.add(MetadataFactory.createRequirementChange(from.size() == 0 ? null : (IRequirement) from.get(0), to.size() == 0 ? null : (IRequirement) to.get(0)));
		}
	}

	protected class RequirementChangeEltHandler extends AbstractHandler {
		private List<IRequirement> requirement;

		public RequirementChangeEltHandler(AbstractHandler parentHandler, String parentId, Attributes attributes, List<IRequirement> from) {
			super(parentHandler, parentId);
			requirement = from;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (REQUIREMENT_ELEMENT.equals(name))
				new RequirementHandler(this, attributes, requirement);
			else {
				invalidElement(name, attributes);
			}
		}

	}

	protected class LifeCycleHandler extends AbstractHandler {
		private List<IRequirement> lifeCycleRequirement;

		public LifeCycleHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, LIFECYCLE);
			lifeCycleRequirement = new ArrayList<>(1);
		}

		public IRequirement getLifeCycleRequirement() {
			if (lifeCycleRequirement.size() == 0)
				return null;
			return lifeCycleRequirement.get(0);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (REQUIREMENT_ELEMENT.equals(name)) {
				new RequirementHandler(this, attributes, lifeCycleRequirement);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class ProvidedCapabilitiesHandler extends AbstractMetadataHandler {
		private List<IProvidedCapability> providedCapabilities;

		public ProvidedCapabilitiesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, PROVIDED_CAPABILITIES_ELEMENT);
			providedCapabilities = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		public IProvidedCapability[] getProvidedCapabilities() {
			return providedCapabilities.toArray(new IProvidedCapability[providedCapabilities.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(PROVIDED_CAPABILITY_ELEMENT)) {
				new ProvidedCapabilityHandler(this, attributes, providedCapabilities);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class ProvidedCapabilityHandler extends AbstractHandler {
		private String namespace;
		private String name;
		private Version version;
		private ProvidedCapabilityPropertiesHandler propertiesHandler;

		private List<IProvidedCapability> capabilities;

		public ProvidedCapabilityHandler(AbstractHandler parentHandler, Attributes attributes, List<IProvidedCapability> capabilities) {
			super(parentHandler, PROVIDED_CAPABILITY_ELEMENT);

			this.capabilities = capabilities;

			String[] values = parseRequiredAttributes(attributes, REQUIRED_PROVIDED_CAPABILITY_ATTRIBUTES);
			this.namespace = values[0];
			this.name = values[1];
			this.version = checkVersion(PROVIDED_CAPABILITY_ELEMENT, VERSION_ATTRIBUTE, values[2]);
		}

		@Override
		public void startElement(String elem, Attributes attributes) {
			switch (elem) {
				case PROPERTIES_ELEMENT :
					this.propertiesHandler = new ProvidedCapabilityPropertiesHandler(this, attributes);
					break;
				default :
					invalidElement(elem, attributes);
					break;
			}
		}

		@Override
		protected void finished() {
			Map<String, Object> properties = (propertiesHandler != null)
					? propertiesHandler.getProperties()
					: new HashMap<>();

			properties.put(namespace, name);
			properties.put(IProvidedCapability.PROPERTY_VERSION, version);
			IProvidedCapability cap = MetadataFactory.createProvidedCapability(namespace, properties);
			capabilities.add(cap);
		}
	}

	protected class ProvidedCapabilityPropertiesHandler extends AbstractMetadataHandler {
		private Map<String, Object> properties;

		public ProvidedCapabilityPropertiesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, PROPERTIES_ELEMENT);
			this.properties = new HashMap<>(getOptionalSize(attributes, 2));
		}

		public Map<String, Object> getProperties() {
			return properties;
		}

		@Override
		public void startElement(String elem, Attributes attributes) {
			switch (elem) {
				case PROPERTY_ELEMENT :
					new ProvidedCapabilityPropertyHandler(this, attributes, properties);
					break;
				default :
					invalidElement(elem, attributes);
					break;
			}
		}
	}

	protected class ProvidedCapabilityPropertyHandler extends AbstractMetadataHandler {
		public ProvidedCapabilityPropertyHandler(AbstractHandler parentHandler, Attributes attributes, Map<String, Object> properties) {
			super(parentHandler, PROPERTY_ELEMENT);

			String[] values = parseAttributes(attributes, PROPERTY_ATTRIBUTES, PROPERTY_OPTIONAL_ATTRIBUTES);

			String name = values[0];
			String value = values[1];
			String type = values[2] == null ? PROPERTY_TYPE_STRING : values[2];

			if (type.startsWith(PROPERTY_TYPE_LIST)) {
				properties.put(name, parseList(type, value));
			} else {
				properties.put(name, parseScalar(type, value));
			}
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			invalidElement(name, attributes);
		}

		private List<Object> parseList(String type, String value) {
			final String elType;
			if (type.length() > PROPERTY_TYPE_LIST.length()) {
				// Strip the leading "List<" and trailing ">"
				elType = type.substring(PROPERTY_TYPE_LIST.length() + 1, type.length() - 1);
			} else {
				elType = PROPERTY_TYPE_STRING;
			}

			return Arrays.stream(value.split("\\s*,\\s*")) //$NON-NLS-1$
					.map(val -> parseScalar(elType, val))
					.collect(toList());
		}

		private Object parseScalar(String type, String value) {
			if (PROPERTY_TYPE_STRING.equals(type)) {
				return value;
			}
			if (PROPERTY_TYPE_INTEGER.equals(type)) {
				return Integer.parseInt(value);
			}
			if (PROPERTY_TYPE_LONG.equals(type)) {
				return Long.parseLong(value);
			}
			if (PROPERTY_TYPE_FLOAT.equals(type)) {
				return Float.parseFloat(value);
			}
			if (PROPERTY_TYPE_DOUBLE.equals(type)) {
				return Double.parseDouble(value);
			}
			if (PROPERTY_TYPE_BYTE.equals(type)) {
				return Byte.parseByte(value);
			}
			if (PROPERTY_TYPE_SHORT.equals(type)) {
				return Short.parseShort(value);
			}
			if (PROPERTY_TYPE_CHARACTER.equals(type)) {
				return value.charAt(0);
			}
			if (PROPERTY_TYPE_BOOLEAN.equals(type)) {
				return Boolean.parseBoolean(value);
			}
			if (PROPERTY_TYPE_VERSION.equals(type)) {
				return Version.create(value);
			}

			// String is the default
			return value;
		}
	}

	protected class HostRequiredCapabilitiesHandler extends AbstractMetadataHandler {
		private List<IRequirement> requiredCapabilities;

		public HostRequiredCapabilitiesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, HOST_REQUIREMENTS_ELEMENT);
			requiredCapabilities = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		public IRequirement[] getHostRequiredCapabilities() {
			return requiredCapabilities.toArray(new IRequirement[requiredCapabilities.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(REQUIREMENT_ELEMENT)) {
				new RequirementHandler(this, attributes, requiredCapabilities);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class MetaRequiredCapabilitiesHandler extends AbstractMetadataHandler {
		private List<IRequirement> requiredCapabilities;

		public MetaRequiredCapabilitiesHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, META_REQUIREMENTS_ELEMENT);
			requiredCapabilities = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		public IRequirement[] getMetaRequiredCapabilities() {
			return requiredCapabilities.toArray(new IRequirement[requiredCapabilities.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(REQUIREMENT_ELEMENT)) {
				new RequirementHandler(this, attributes, requiredCapabilities);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class RequirementsHandler extends AbstractMetadataHandler {
		private List<IRequirement> requirements;

		public RequirementsHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, REQUIREMENTS_ELEMENT);
			requirements = new ArrayList<>(getOptionalSize(attributes, 4));
		}

		public IRequirement[] getRequirements() {
			return requirements.toArray(new IRequirement[requirements.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			switch (name) {
				case REQUIREMENT_ELEMENT :
					new RequirementHandler(this, attributes, requirements);
					break;
				case REQUIREMENT_PROPERTIES_ELEMENT :
					new RequirementPropertiesHandler(this, attributes, requirements);
					break;
				default :
					invalidElement(name, attributes);
					break;
			}
		}
	}

	protected class RequirementHandler extends AbstractHandler {
		private List<IRequirement> capabilities;

		// Expression based requirement
		private String match;
		private String matchParams;

		// Simple requirement
		private String namespace;
		private String name;
		private VersionRange range;

		private int min;
		private int max;
		private boolean greedy;

		private TextHandler filterHandler = null;
		private TextHandler descriptionHandler = null;

		public RequirementHandler(AbstractHandler parentHandler, Attributes attributes, List<IRequirement> capabilities) {
			super(parentHandler, REQUIREMENT_ELEMENT);
			this.capabilities = capabilities;

			// Version range requirement
			if (attributes.getIndex(NAMESPACE_ATTRIBUTE) >= 0) {
				String[] values = parseAttributes(attributes, REQIURED_CAPABILITY_ATTRIBUTES, REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTES);
				namespace = values[0];
				name = values[1];
				range = checkVersionRange(REQUIREMENT_ELEMENT, VERSION_RANGE_ATTRIBUTE, values[2]);
				boolean isOptional = checkBoolean(REQUIREMENT_ELEMENT, REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTE, values[3], false).booleanValue();
				min = isOptional ? 0 : 1;
				boolean isMultiple = checkBoolean(REQUIREMENT_ELEMENT, REQUIRED_CAPABILITY_MULTIPLE_ATTRIBUTE, values[4], false).booleanValue();
				max = isMultiple ? Integer.MAX_VALUE : 1;
				greedy = checkBoolean(REQUIREMENT_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[5], true).booleanValue();
			}
			// IU match expression requirement
			else {
				String[] values = parseAttributes(attributes, REQUIRED_IU_MATCH_ATTRIBUTES, REQUIRED_IU_MATCH_OPTIONAL_ATTRIBUTES);
				match = values[0];
				matchParams = values[1];
				min = values[2] == null ? 1 : checkInteger(REQUIREMENT_ELEMENT, MIN_ATTRIBUTE, values[2]);
				max = values[3] == null ? 1 : checkInteger(REQUIREMENT_ELEMENT, MAX_ATTRIBUTE, values[3]);
				greedy = checkBoolean(REQUIREMENT_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[4], true).booleanValue();
			}
		}

		@Override
		public void startElement(String elem, Attributes attributes) {
			switch (elem) {
				case REQUIREMENT_FILTER_ELEMENT:
					filterHandler = new TextHandler(this, REQUIREMENT_FILTER_ELEMENT, attributes);
					break;
				case REQUIREMENT_DESCRIPTION_ELEMENT:
					descriptionHandler = new TextHandler(this, REQUIREMENT_DESCRIPTION_ELEMENT, attributes);
					break;
				default:
					invalidElement(elem, attributes);
					break;
			}
		}

		@Override
		protected void finished() {
			if (!isValidXML())
				return;
			IMatchExpression<IInstallableUnit> filter = null;
			if (filterHandler != null) {
				try {
					filter = InstallableUnit.parseFilter(filterHandler.getText());
				} catch (ExpressionParseException e) {
					if (removeWhiteSpace(filterHandler.getText()).equals("(&(|)(|)(|))")) {//$NON-NLS-1$
						// We could log this I guess
					} else {
						throw e;
					}
				}
			}
			String description = descriptionHandler == null ? null : descriptionHandler.getText();
			IRequirement requirement;
			if (match != null) {
				IMatchExpression<IInstallableUnit> matchExpr = createMatchExpression(match, matchParams);
				requirement = MetadataFactory.createRequirement(matchExpr, filter, min, max, greedy, description);
			} else {
				requirement = MetadataFactory.createRequirement(namespace, name, range, filter, min, max, greedy, description);
			}
			capabilities.add(requirement);
		}

		private String removeWhiteSpace(String s) {
			if (s == null)
				return ""; //$NON-NLS-1$
			StringBuilder builder = new StringBuilder();
			for (int i = 0; i < s.length(); i++) {
				if (s.charAt(i) != ' ')
					builder.append(s.charAt(i));
			}
			return builder.toString();
		}
	}

	protected class RequirementPropertiesHandler extends AbstractHandler {
		private List<IRequirement> requirements;

		private String namespace;
		private String match;
		private int min;
		private int max;
		private boolean greedy;

		private TextHandler filterHandler;
		private TextHandler descriptionHandler;

		public RequirementPropertiesHandler(AbstractHandler parentHandler, Attributes attributes, List<IRequirement> requirements) {
			super(parentHandler, REQUIREMENT_PROPERTIES_ELEMENT);
			this.requirements = requirements;

			String[] values = parseAttributes(attributes, REQIURED_PROPERTIES_MATCH_ATTRIBUTES, REQIURED_PROPERTIES_MATCH_OPTIONAL_ATTRIBUTES);
			namespace = values[0];
			match = values[1];
			min = (values[2] == null) ? 1 : checkInteger(REQUIREMENT_PROPERTIES_ELEMENT, MIN_ATTRIBUTE, values[2]);
			max = (values[3] == null) ? 1 : checkInteger(REQUIREMENT_PROPERTIES_ELEMENT, MAX_ATTRIBUTE, values[3]);
			greedy = checkBoolean(REQUIREMENT_PROPERTIES_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[4], true).booleanValue();
		}

		@Override
		public void startElement(String elem, Attributes attributes) {
			switch (elem) {
				case REQUIREMENT_FILTER_ELEMENT :
					filterHandler = new TextHandler(this, REQUIREMENT_FILTER_ELEMENT, attributes);
					break;
				case REQUIREMENT_DESCRIPTION_ELEMENT :
					descriptionHandler = new TextHandler(this, REQUIREMENT_DESCRIPTION_ELEMENT, attributes);
					break;
				default :
					invalidElement(elem, attributes);
					break;
			}
		}

		@Override
		protected void finished() {
			if (!isValidXML()) {
				return;
			}

			IMatchExpression<IInstallableUnit> filter = null;
			if (filterHandler != null) {
				try {
					filter = InstallableUnit.parseFilter(filterHandler.getText());
				} catch (ExpressionParseException e) {
					if (removeWhiteSpace(filterHandler.getText()).equals("(&(|)(|)(|))")) {//$NON-NLS-1$
						// We could log this I guess
					} else {
						throw e;
					}
				}
			}

			String description = (descriptionHandler != null) ? descriptionHandler.getText() : null;

			IFilterExpression attrMatch = ExpressionUtil.parseLDAP(match);
			IRequirement requirement = MetadataFactory.createRequirement(namespace, attrMatch, filter, min, max, greedy, description);
			requirements.add(requirement);
		}

		private String removeWhiteSpace(String s) {
			if (s == null)
				return ""; //$NON-NLS-1$
			StringBuilder builder = new StringBuilder();
			for (int i = 0; i < s.length(); i++) {
				if (s.charAt(i) != ' ')
					builder.append(s.charAt(i));
			}
			return builder.toString();
		}
	}

	protected class ArtifactsHandler extends AbstractHandler {

		private List<IArtifactKey> artifacts;

		public ArtifactsHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, ARTIFACT_KEYS_ELEMENT);
			String size = parseOptionalAttribute(attributes, COLLECTION_SIZE_ATTRIBUTE);
			artifacts = (size != null ? new ArrayList<>(Integer.parseInt(size)) : new ArrayList<>(4));
		}

		public IArtifactKey[] getArtifactKeys() {
			return artifacts.toArray(new IArtifactKey[artifacts.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(ARTIFACT_KEY_ELEMENT)) {
				new ArtifactHandler(this, attributes, artifacts);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class ArtifactHandler extends AbstractHandler {

		private final String[] required = new String[] {CLASSIFIER_ATTRIBUTE, ID_ATTRIBUTE, VERSION_ATTRIBUTE};

		public ArtifactHandler(AbstractHandler parentHandler, Attributes attributes, List<IArtifactKey> artifacts) {
			super(parentHandler, ARTIFACT_KEY_ELEMENT);
			String[] values = parseRequiredAttributes(attributes, required);
			Version version = checkVersion(ARTIFACT_KEY_ELEMENT, VERSION_ATTRIBUTE, values[2]);
			artifacts.add(new ArtifactKey(values[0], values[1], version));
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			invalidElement(name, attributes);
		}
	}

	protected class TouchpointTypeHandler extends AbstractHandler {

		private final String[] required = new String[] {ID_ATTRIBUTE, VERSION_ATTRIBUTE};

		ITouchpointType touchpointType = null;

		public TouchpointTypeHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, TOUCHPOINT_TYPE_ELEMENT);
			String[] values = parseRequiredAttributes(attributes, required);
			Version version = checkVersion(TOUCHPOINT_TYPE_ELEMENT, VERSION_ATTRIBUTE, values[1]);
			touchpointType = MetadataFactory.createTouchpointType(values[0], version);
		}

		public ITouchpointType getTouchpointType() {
			return touchpointType;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			invalidElement(name, attributes);
		}
	}

	protected class TouchpointDataHandler extends AbstractHandler {

		ITouchpointData touchpointData = null;

		List<TouchpointInstructionsHandler> data = null;

		public TouchpointDataHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, TOUCHPOINT_DATA_ELEMENT);
			String size = parseOptionalAttribute(attributes, COLLECTION_SIZE_ATTRIBUTE);
			data = (size != null ? new ArrayList<>(Integer.parseInt(size)) : new ArrayList<>(4));
		}

		public ITouchpointData[] getTouchpointData() {
			ITouchpointData[] result = new ITouchpointData[data.size()];
			for (int i = 0; i < result.length; i++)
				result[i] = data.get(i).getTouchpointData();
			return result;
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(TOUCHPOINT_DATA_INSTRUCTIONS_ELEMENT)) {
				data.add(new TouchpointInstructionsHandler(this, attributes, data));
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class TouchpointInstructionsHandler extends AbstractHandler {

		Map<String, ITouchpointInstruction> instructions = null;

		public TouchpointInstructionsHandler(AbstractHandler parentHandler, Attributes attributes, List<TouchpointInstructionsHandler> data) {
			super(parentHandler, TOUCHPOINT_DATA_INSTRUCTIONS_ELEMENT);
			String size = parseOptionalAttribute(attributes, COLLECTION_SIZE_ATTRIBUTE);
			instructions = (size != null ? new LinkedHashMap<>(Integer.parseInt(size)) : new LinkedHashMap<>(4));
		}

		public ITouchpointData getTouchpointData() {
			return MetadataFactory.createTouchpointData(instructions);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(TOUCHPOINT_DATA_INSTRUCTION_ELEMENT)) {
				new TouchpointInstructionHandler(this, attributes, instructions);
			} else {
				invalidElement(name, attributes);
			}
		}
	}

	protected class TouchpointInstructionHandler extends TextHandler {

		private final String[] required = new String[] {TOUCHPOINT_DATA_INSTRUCTION_KEY_ATTRIBUTE};
		private final String[] optional = new String[] {TOUCHPOINT_DATA_INSTRUCTION_IMPORT_ATTRIBUTE};

		Map<String, ITouchpointInstruction> instructions = null;
		String key = null;
		String qualifier = null;

		public TouchpointInstructionHandler(AbstractHandler parentHandler, Attributes attributes, Map<String, ITouchpointInstruction> instructions) {
			super(parentHandler, TOUCHPOINT_DATA_INSTRUCTION_ELEMENT);
			String[] values = parseAttributes(attributes, required, optional);
			key = values[0];
			qualifier = values[1];
			this.instructions = instructions;
		}

		@Override
		protected void finished() {
			if (isValidXML()) {
				if (key != null) {
					instructions.put(key, MetadataFactory.createTouchpointInstruction(getText(), qualifier));
				}
			}
		}
	}

	protected class UpdateDescriptorHandler extends TextHandler {
		private final String[] requiredSimple = new String[] {ID_ATTRIBUTE, VERSION_RANGE_ATTRIBUTE};
		private final String[] optionalSimple = new String[] {UPDATE_DESCRIPTOR_SEVERITY, DESCRIPTION_ATTRIBUTE};

		private final String[] requiredComplex = new String[] {MATCH_ATTRIBUTE};
		private final String[] optionalComplex = new String[] {UPDATE_DESCRIPTOR_SEVERITY, DESCRIPTION_ATTRIBUTE, MATCH_PARAMETERS_ATTRIBUTE};

		private IUpdateDescriptor descriptor;

		public UpdateDescriptorHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, INSTALLABLE_UNIT_ELEMENT);
			boolean simple = attributes.getIndex(ID_ATTRIBUTE) >= 0;
			String[] values;
			int severityIdx;
			String description;
			if (simple) {
				values = parseAttributes(attributes, requiredSimple, optionalSimple);
				severityIdx = 2;
				description = values[3];
			} else {
				values = parseAttributes(attributes, requiredComplex, optionalComplex);
				severityIdx = 1;
				description = values[2];
			}

			int severity;
			try {
				severity = Integer.parseInt(values[severityIdx]);
			} catch (NumberFormatException e) {
				invalidAttributeValue(UPDATE_DESCRIPTOR_ELEMENT, UPDATE_DESCRIPTOR_SEVERITY, values[severityIdx]);
				severity = IUpdateDescriptor.NORMAL;
			}
			URI location = parseURIAttribute(attributes, false);

			if (simple) {
				VersionRange range = checkVersionRange(REQUIREMENT_ELEMENT, VERSION_RANGE_ATTRIBUTE, values[1]);
				descriptor = MetadataFactory.createUpdateDescriptor(values[0], range, severity, description, location);
			} else {
				IMatchExpression<IInstallableUnit> r = createMatchExpression(values[0], values[3]);
				descriptor = MetadataFactory.createUpdateDescriptor(Collections.singleton(r), severity, description, location);
			}
		}

		public IUpdateDescriptor getUpdateDescriptor() {
			return descriptor;
		}
	}

	/**
	 * 	Handler for a list of licenses.
	 */
	protected class LicensesHandler extends AbstractHandler {

		// Note this handler is set up to handle multiple license elements, but for now
		// the API for IInstallableUnit only reflects one.
		// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=216911
		private List<ILicense> licenses;

		public LicensesHandler(ContentHandler parentHandler, Attributes attributes) {
			super(parentHandler, LICENSES_ELEMENT);
			String size = parseOptionalAttribute(attributes, COLLECTION_SIZE_ATTRIBUTE);
			licenses = (size != null ? new ArrayList<>(Integer.parseInt(size)) : new ArrayList<>(2));
		}

		public ILicense[] getLicenses() {
			if (licenses.size() == 0)
				return NO_LICENSES;
			return licenses.toArray(new ILicense[licenses.size()]);
		}

		@Override
		public void startElement(String name, Attributes attributes) {
			if (name.equals(LICENSE_ELEMENT)) {
				new LicenseHandler(this, attributes, licenses);
			} else {
				invalidElement(name, attributes);
			}
		}

	}

	/**
	 * 	Handler for a license in an list of licenses.
	 */
	protected class LicenseHandler extends TextHandler {

		URI location = null;

		private final List<ILicense> licenses;

		public LicenseHandler(AbstractHandler parentHandler, Attributes attributes, List<ILicense> licenses) {
			super(parentHandler, LICENSE_ELEMENT);
			location = parseURIAttribute(attributes, false);
			this.licenses = licenses;
		}

		@Override
		protected void finished() {
			if (isValidXML()) {
				licenses.add(MetadataFactory.createLicense(location, getText()));
			}
		}
	}

	/**
	 * 	Handler for a copyright.
	 */
	protected class CopyrightHandler extends TextHandler {

		URI location = null;
		private ICopyright copyright;

		public CopyrightHandler(AbstractHandler parentHandler, Attributes attributes) {
			super(parentHandler, COPYRIGHT_ELEMENT);
			location = parseURIAttribute(attributes, false);
		}

		@Override
		protected void finished() {
			if (isValidXML()) {
				copyright = MetadataFactory.createCopyright(location, getText());
			}
		}

		public ICopyright getCopyright() {
			return copyright;
		}
	}

	static IMatchExpression<IInstallableUnit> createMatchExpression(String match, String matchParams) {
		IExpressionFactory factory = ExpressionUtil.getFactory();
		IExpression expr = ExpressionUtil.parse(match);
		Object[] params;
		if (matchParams == null)
			params = new Object[0];
		else {
			IExpression[] arrayExpr = ExpressionUtil.getOperands(ExpressionUtil.parse(matchParams));
			params = new Object[arrayExpr.length];
			for (int idx = 0; idx < arrayExpr.length; ++idx)
				params[idx] = arrayExpr[idx].evaluate(null);
		}
		return factory.matchExpression(expr, params);
	}
}
