Bug 313553 - [publisher] publish metadata for Provide-Capability and Require-Capability

Generalize to include attributes.

Change-Id: I1135b89842185ed0e3197bfbcf4625a0f4f73510
Signed-off-by: Todor Boev <rinsvind@gmail.com>
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
index 8a324a2..be7aea6 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2011 IBM Corporation and others.
+ * Copyright (c) 2007, 2017 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -12,6 +12,9 @@
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.metadata;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
 import org.eclipse.core.runtime.Assert;
 import org.eclipse.equinox.p2.metadata.IProvidedCapability;
 import org.eclipse.equinox.p2.metadata.Version;
@@ -26,33 +29,71 @@
 	public static final String MEMBER_VERSION = "version"; //$NON-NLS-1$
 	public static final String MEMBER_NAMESPACE = "namespace"; //$NON-NLS-1$
 
-	private final String name;
 	private final String namespace;
-	private final Version version;
+	private final Map<String, Object> attributes;
+
+	public ProvidedCapability(String namespace, Map<String, Object> attrs) {
+		Assert.isNotNull(namespace, NLS.bind(Messages.provided_capability_namespace_not_defined, null));
+		this.namespace = namespace;
+
+		Assert.isNotNull(attrs);
+		Assert.isTrue(!attrs.isEmpty());
+		Assert.isTrue(!attrs.containsKey(MEMBER_NAMESPACE));
+		this.attributes = new HashMap<>(attrs);
+
+		if (!attributes.containsKey(MEMBER_NAME)) {
+			// It is common for a capability to have a main attribute under a key
+			// with value the same as the capability namespace. Use as "name" if present.
+			Assert.isTrue(attributes.containsKey(namespace));
+			attributes.put(MEMBER_NAME, attributes.get(namespace));
+		}
+
+		Object version = attributes.get(MEMBER_VERSION);
+		if (version == null) {
+			attributes.put(MEMBER_VERSION, Version.emptyVersion);
+		} else if (version instanceof org.osgi.framework.Version) {
+			org.osgi.framework.Version osgiVer = (org.osgi.framework.Version) version;
+			attributes.put(
+					MEMBER_VERSION,
+					Version.createOSGi(osgiVer.getMajor(), osgiVer.getMinor(), osgiVer.getMicro(), osgiVer.getQualifier()));
+		} else {
+			Assert.isTrue(version instanceof Version);
+		}
+	}
 
 	public ProvidedCapability(String namespace, String name, Version version) {
 		Assert.isNotNull(namespace, NLS.bind(Messages.provided_capability_namespace_not_defined, null));
 		Assert.isNotNull(name, NLS.bind(Messages.provided_capability_name_not_defined, namespace));
 		this.namespace = namespace;
-		this.name = name;
-		this.version = version == null ? Version.emptyVersion : version;
+		this.attributes = new HashMap<>();
+		attributes.put(MEMBER_NAME, name);
+		attributes.put(MEMBER_VERSION, version == null ? Version.emptyVersion : version);
 	}
 
 	public boolean equals(Object other) {
-		if (other == null)
+		if (other == null) {
 			return false;
-		if (!(other instanceof IProvidedCapability))
+		}
+
+		if (!(other instanceof IProvidedCapability)) {
 			return false;
+		}
+
 		IProvidedCapability otherCapability = (IProvidedCapability) other;
-		if (!(namespace.equals(otherCapability.getNamespace())))
+
+		if (!(namespace.equals(otherCapability.getNamespace()))) {
 			return false;
-		if (!(name.equals(otherCapability.getName())))
+		}
+
+		if (!(attributes.equals(otherCapability.getAttributes()))) {
 			return false;
-		return version.equals(otherCapability.getVersion());
+		}
+
+		return true;
 	}
 
 	public String getName() {
-		return name;
+		return (String) attributes.get(MEMBER_NAME);
 	}
 
 	public String getNamespace() {
@@ -60,28 +101,37 @@
 	}
 
 	public Version getVersion() {
-		return version;
+		return (Version) attributes.get(MEMBER_VERSION);
+	}
+
+	public Map<String, Object> getAttributes() {
+		return attributes;
 	}
 
 	public int hashCode() {
-		return namespace.hashCode() * name.hashCode() * version.hashCode();
+		return namespace.hashCode() * attributes.hashCode();
 	}
 
 	public String toString() {
-		return namespace + '/' + name + '/' + version;
+		StringBuilder str = new StringBuilder();
+		str.append(namespace);
+
+		for (Entry<String, Object> attr : attributes.entrySet()) {
+			String key = attr.getKey();
+			Object val = attr.getValue();
+			String type = val.getClass().getSimpleName();
+
+			str.append("; ").append(key).append(":").append(type).append("=").append(val); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+
+		return str.toString();
 	}
 
 	public Object getMember(String memberName) {
-		// It is OK to use identity comparisons here since
-		// a) All constant valued strings are always interned
-		// b) The Member constructor always interns the name
-		//
-		if (MEMBER_NAME == memberName)
-			return name;
-		if (MEMBER_VERSION == memberName)
-			return version;
-		if (MEMBER_NAMESPACE == memberName)
-			return namespace;
-		throw new IllegalArgumentException("No such member: " + memberName); //$NON-NLS-1$
+		Object res = memberName.equals(MEMBER_NAMESPACE) ? namespace : attributes.get(memberName);
+		if (res == null) {
+			throw new IllegalArgumentException("No such member: " + memberName); //$NON-NLS-1$
+		}
+		return res;
 	}
 }
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
index 7848c2d..e895d5b 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
@@ -10,6 +10,8 @@
 ******************************************************************************/
 package org.eclipse.equinox.p2.metadata;
 
+import java.util.Map;
+
 /**
  * Describes a capability that is exposed by an installable unit. These capabilities
  * can satisfy the dependencies of other installable units, causing the unit
@@ -29,26 +31,33 @@
 
 	/**
 	 * 
-	 * @return String
+	 * @return String the special "name" attribute of this capability.
 	 * @noreference This method is not intended to be referenced by clients.
 	 */
 	public String getName();
 
 	/**
 	 * 
-	 * @return String
+	 * @return String the namespace of this capability.
 	 * @noreference This method is not intended to be referenced by clients.
 	 */
 	public String getNamespace();
 
 	/**
 	 * 
-	 * @return String
+	 * @return String the special "version" attribute of this capability.
 	 * @noreference This method is not intended to be referenced by clients.
 	 */
 	public Version getVersion();
 
 	/**
+	 * 
+	 * @return A full description of this capability
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public Map<String, Object> getAttributes();
+
+	/**
 	 * Returns whether this provided capability is equal to the given object.
 	 * 
 	 * This method returns <i>true</i> if:
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java
index 4cbaffb..061fd11 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java
@@ -430,6 +430,17 @@
 	}
 
 	/**
+	 * Returns a {@link IProvidedCapability} with the given values.
+	 * 
+	 * @param namespace The capability namespace
+	 * @param attributes The description of the capability
+	 * @since 2.4
+	 */
+	public static IProvidedCapability createProvidedCapability(String namespace, Map<String, Object> attributes) {
+		return new ProvidedCapability(namespace, attributes);
+	}
+
+	/**
 	 * Returns a {@link IRequirement} with the given values.
 	 * 
 	 * @param namespace The capability namespace
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java
index f715a45..e743c47 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java
@@ -177,14 +177,15 @@
 
 	class SPIProvidedCapability implements IProvidedCapability {
 
-		String namespace = null;
-		String name = null;
-		Version version = null;
+		String namespace;
+		Map<String, Object> attributes;
 
 		public SPIProvidedCapability(String namespace, String name, Version version) {
 			this.namespace = namespace;
-			this.name = name;
-			this.version = version;
+
+			this.attributes = new HashMap<String, Object>();
+			attributes.put(ProvidedCapability.MEMBER_NAME, name);
+			attributes.put(ProvidedCapability.MEMBER_VERSION, version);
 		}
 
 		@Override
@@ -196,14 +197,14 @@
 			IProvidedCapability otherCapability = (IProvidedCapability) other;
 			if (!(namespace.equals(otherCapability.getNamespace())))
 				return false;
-			if (!(name.equals(otherCapability.getName())))
+			if (!(attributes.equals(otherCapability.getAttributes())))
 				return false;
 			return true;
 		}
 
 		@Override
 		public String getName() {
-			return this.name;
+			return (String) attributes.get(ProvidedCapability.MEMBER_NAME);
 		}
 
 		@Override
@@ -213,13 +214,16 @@
 
 		@Override
 		public Version getVersion() {
-			return this.version;
+			return (Version) attributes.get(ProvidedCapability.MEMBER_VERSION);
 		}
 
 		public boolean satisfies(IRequirement candidate) {
 			return false;
 		}
 
+		public Map<String, Object> getAttributes() {
+			return attributes;
+		}
 	}
 
 	class SPIInstallableUnit implements IInstallableUnit {