Bug 323427 -  [RFC 154] Support for generic capabilities and requirements
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateBuilder.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateBuilder.java
index 5dd77bf..1062e8e 100644
--- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateBuilder.java
+++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateBuilder.java
@@ -35,7 +35,7 @@
 	private static final String ATTR_TYPE_LONG = "long"; //$NON-NLS-1$
 	private static final String ATTR_TYPE_DOUBLE = "double"; //$NON-NLS-1$
 	private static final String ATTR_TYPE_SET = "set"; //$NON-NLS-1$
-	private static final String ATTR_TYPE_LIST = "List<"; //$NON-NLS-1$
+	private static final String ATTR_TYPE_LIST = "List"; //$NON-NLS-1$
 	private static final String OPTIONAL_ATTR = "optional"; //$NON-NLS-1$
 	private static final String MULTIPLE_ATTR = "multiple"; //$NON-NLS-1$
 	private static final String TRUE = "true"; //$NON-NLS-1$
@@ -367,8 +367,8 @@
 			int colonIndex = key.indexOf(':');
 			String type = ATTR_TYPE_STRING;
 			if (colonIndex > 0) {
-				type = key.substring(colonIndex + 1);
-				key = key.substring(0, colonIndex);
+				type = key.substring(colonIndex + 1).trim();
+				key = key.substring(0, colonIndex).trim();
 			}
 			if (!definedAttr) {
 				if (arbitraryAttrs == null)
@@ -380,40 +380,51 @@
 	}
 
 	private static Object convertValue(String type, String value) {
-		Object convertValue = value;
+
 		if (ATTR_TYPE_STRING.equalsIgnoreCase(type))
-			convertValue = value;
-		else if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type))
-			convertValue = new Double(value);
+			return value;
+
+		value = value.trim();
+		if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type))
+			return new Double(value);
 		else if (ATTR_TYPE_LONG.equalsIgnoreCase(type))
-			convertValue = new Long(value);
+			return new Long(value);
 		else if (ATTR_TYPE_URI.equalsIgnoreCase(type))
 			try {
 				Class uriClazz = Class.forName("java.net.URI"); //$NON-NLS-1$
 				Constructor constructor = uriClazz.getConstructor(new Class[] {String.class});
-				convertValue = constructor.newInstance(new Object[] {value});
+				return constructor.newInstance(new Object[] {value});
 			} catch (ClassNotFoundException e) {
 				// oh well cannot support; just use string
-				convertValue = value;
+				return value;
 			} catch (RuntimeException e) { // got some reflection exception
 				throw e;
 			} catch (Exception e) {
 				throw new RuntimeException(e.getMessage(), e);
 			}
 		else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type))
-			convertValue = new Version(value);
+			return new Version(value);
 		else if (ATTR_TYPE_SET.equalsIgnoreCase(type))
-			convertValue = ManifestElement.getArrayFromList(value, ","); //$NON-NLS-1$
-		else if (type.startsWith(ATTR_TYPE_LIST) && type.endsWith(">")) { //$NON-NLS-1$
-			String componentType = type.substring(ATTR_TYPE_LIST.length(), type.length() - 1);
-			String[] list = ManifestElement.getArrayFromList(value, ","); //$NON-NLS-1$
-			List<Object> components = new ArrayList<Object>();
-			for (String component : list) {
-				components.add(convertValue(componentType, component));
-			}
-			convertValue = components;
+			return ManifestElement.getArrayFromList(value, ","); //$NON-NLS-1$
+
+		// assume list type, anything else will throw an exception
+		Tokenizer listTokenizer = new Tokenizer(type);
+		String listType = listTokenizer.getToken("<"); //$NON-NLS-1$
+		if (!ATTR_TYPE_LIST.equalsIgnoreCase(listType))
+			throw new RuntimeException("Unsupported type: " + type); //$NON-NLS-1$
+		char c = listTokenizer.getChar();
+		String componentType = ATTR_TYPE_STRING;
+		if (c == '<') {
+			componentType = listTokenizer.getToken(">"); //$NON-NLS-1$
+			if (listTokenizer.getChar() != '>')
+				throw new RuntimeException("Invalid type, missing ending '>' : " + type); //$NON-NLS-1$
 		}
-		return convertValue;
+		List<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$
+		List<Object> components = new ArrayList<Object>();
+		for (String component : tokens) {
+			components.add(convertValue(componentType, component));
+		}
+		return components;
 	}
 
 	private static HostSpecification createHostSpecification(ManifestElement spec, StateImpl state) {
diff --git a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/framework/internal/core/Tokenizer.java b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/framework/internal/core/Tokenizer.java
index ff4d101..2cbf602 100644
--- a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/framework/internal/core/Tokenizer.java
+++ b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/framework/internal/core/Tokenizer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * Copyright (c) 2003, 2010 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
@@ -11,6 +11,9 @@
 
 package org.eclipse.osgi.framework.internal.core;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Simple tokenizer class. Used to parse data.
  */
@@ -62,7 +65,41 @@
 		return (null);
 	}
 
-	public String getString(String terminals) {
+	public String getEscapedToken(String terminals) {
+		char[] val = value;
+		int cur = cursor;
+		if (cur >= max)
+			return null;
+		StringBuffer sb = new StringBuffer();
+		char c;
+		for (; cur < max; cur++) {
+			c = val[cur];
+			// this is an escaped char
+			if (c == '\\') {
+				cur++; // skip the escape char
+				if (cur == max)
+					break;
+				c = val[cur]; // include the escaped char
+			} else if (terminals.indexOf(c) != -1) {
+				break;
+			}
+			sb.append(c);
+		}
+
+		cursor = cur;
+		return sb.toString();
+	}
+
+	public List<String> getEscapedTokens(String terminals) {
+		List<String> result = new ArrayList<String>();
+		for (String token = getEscapedToken(terminals); token != null; token = getEscapedToken(terminals)) {
+			result.add(token);
+			getChar(); // consume terminal
+		}
+		return result;
+	}
+
+	public String getString(String terminals, String preserveEscapes) {
 		skipWhiteSpace();
 		char[] val = value;
 		int cur = cursor;
@@ -82,6 +119,8 @@
 						if (cur == max)
 							break;
 						c = val[cur]; // include the escaped char
+						if (preserveEscapes != null && preserveEscapes.indexOf(c) != -1)
+							sb.append('\\'); // must preserve escapes for c
 					} else if (c == '\"') {
 						break;
 					}
@@ -104,6 +143,10 @@
 		return (null);
 	}
 
+	public String getString(String terminals) {
+		return getString(terminals, null);
+	}
+
 	public char getChar() {
 		int cur = cursor;
 		if (cur < max) {
diff --git a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/util/ManifestElement.java b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/util/ManifestElement.java
index b5cbbea..db3185a 100644
--- a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/util/ManifestElement.java
+++ b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/util/ManifestElement.java
@@ -396,7 +396,17 @@
 					} else
 						directive = true;
 				}
-				String val = tokenizer.getString(";,"); //$NON-NLS-1$
+				// determine if the attribute is the form attr:List<type>
+				String preserveEscapes = null;
+				if (!directive && next.indexOf("List") > 0) { //$NON-NLS-1$
+					Tokenizer listTokenizer = new Tokenizer(next);
+					String attrKey = listTokenizer.getToken(":"); //$NON-NLS-1$
+					if (attrKey != null && listTokenizer.getChar() == ':' && "List".equals(listTokenizer.getToken("<"))) { //$NON-NLS-1$//$NON-NLS-2$
+						// we assume we must preserve escapes for , and "
+						preserveEscapes = "\\,"; //$NON-NLS-1$
+					}
+				}
+				String val = tokenizer.getString(";,", preserveEscapes); //$NON-NLS-1$
 				if (val == null)
 					throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);