Bug 406526 - Allow setting user library version

Change-Id: I2bb7be9998c4afdcef3d4040d97995ff142041c9
Signed-off-by: Michal Niewrzal <michal.n@zend.com>
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibrary.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibrary.java
index 7954c4d..1cb3465 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibrary.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibrary.java
@@ -15,7 +15,9 @@
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Map;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -47,14 +49,25 @@
 	private static final String TAG_PATH = "path"; //$NON-NLS-1$
 	private static final String TAG_ARCHIVE = "archive"; //$NON-NLS-1$
 	private static final String TAG_SYSTEMLIBRARY = "systemlibrary"; //$NON-NLS-1$
+	private static final String ATTRIBUTE_PREFIX = "__attribute__"; //$NON-NLS-1$
 
 	private boolean isSystemLibrary;
 	private IBuildpathEntry[] entries;
+	private Map<String, String> attributes;
 
 	public UserLibrary(IBuildpathEntry[] entries, boolean isSystemLibrary) {
+		this(entries, isSystemLibrary, null);
+	}
+
+	public UserLibrary(IBuildpathEntry[] entries, boolean isSystemLibrary,
+			Map<String, String> attributes) {
 		Assert.isNotNull(entries);
 		this.entries = entries;
 		this.isSystemLibrary = isSystemLibrary;
+		this.attributes = new HashMap<String, String>();
+		if (attributes != null) {
+			this.attributes.putAll(attributes);
+		}
 	}
 
 	public IBuildpathEntry[] getEntries() {
@@ -65,6 +78,14 @@
 		return this.isSystemLibrary;
 	}
 
+	public String getAttribute(String name) {
+		return attributes.get(name);
+	}
+
+	public Map<String, String> getAttributes() {
+		return attributes;
+	}
+
 	/*
 	 * (non-Javadoc)
 	 * 
@@ -74,11 +95,13 @@
 		if (obj != null && obj.getClass() == getClass()) {
 			UserLibrary other = (UserLibrary) obj;
 			if (this.entries.length == other.entries.length
-					&& this.isSystemLibrary == other.isSystemLibrary) {
-				for (int i = 0; i < this.entries.length; i++) {
-					if (!this.entries[i].equals(other.entries[i])) {
-						return false;
-					}
+					&& this.isSystemLibrary == other.isSystemLibrary
+					&& attributes.size() == other.attributes.size()) {
+				if (!Arrays.equals(this.entries, other.entries)) {
+					return false;
+				}
+				if (!this.attributes.equals(other.attributes)) {
+					return false;
 				}
 				return true;
 			}
@@ -93,17 +116,23 @@
 	 */
 	public int hashCode() {
 		int hashCode = 0;
-		if (this.isSystemLibrary) {
+		if (this.isSystemLibrary()) {
 			hashCode++;
 		}
 		for (int i = 0; i < this.entries.length; i++) {
-			hashCode = hashCode * 17 + this.entries.hashCode();
+			hashCode = hashCode * 17 + this.entries[i].hashCode();
 		}
 		return hashCode;
 	}
 
 	public static String serialize(IBuildpathEntry[] entries,
 			boolean isSystemLibrary) throws IOException {
+		return serialize(entries, isSystemLibrary, null);
+	}
+
+	public static String serialize(IBuildpathEntry[] entries,
+			boolean isSystemLibrary, Map<String, String> attributes)
+			throws IOException {
 		ByteArrayOutputStream s = new ByteArrayOutputStream();
 		OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$
 		XMLWriter xmlWriter = new XMLWriter(writer, null/*
@@ -114,15 +143,20 @@
 																 * version
 																 */);
 
-		HashMap library = new HashMap();
+		HashMap<String, String> library = new HashMap<String, String>();
 		library.put(TAG_VERSION, String.valueOf(CURRENT_VERSION));
 		library.put(TAG_SYSTEMLIBRARY, String.valueOf(isSystemLibrary));
+		if (attributes != null) {
+			for (String key : attributes.keySet()) {
+				library.put(ATTRIBUTE_PREFIX + key, attributes.get(key));
+			}
+		}
 		xmlWriter.printTag(TAG_USERLIBRARY, library, true, true, false);
 
 		for (int i = 0, length = entries.length; i < length; ++i) {
 			BuildpathEntry cpEntry = (BuildpathEntry) entries[i];
 
-			HashMap archive = new HashMap();
+			HashMap<String, String> archive = new HashMap<String, String>();
 			archive.put(TAG_PATH, cpEntry.getPath().toString());
 
 			boolean hasExtraAttributes = cpEntry.extraAttributes != null
@@ -191,10 +225,20 @@
 		boolean isSystem = Boolean.valueOf(
 				cpElement.getAttribute(TAG_SYSTEMLIBRARY)).booleanValue();
 
+		Map<String, String> attributes = new HashMap<String, String>();
+		for (int i = 0; i < cpElement.getAttributes().getLength(); i++) {
+			Node node = cpElement.getAttributes().item(i);
+			if (node.getNodeName().startsWith(ATTRIBUTE_PREFIX)) {
+				String name = node.getNodeName().substring(
+						ATTRIBUTE_PREFIX.length());
+				attributes.put(name, node.getNodeValue());
+			}
+		}
+
 		NodeList list = cpElement.getChildNodes();
 		int length = list.getLength();
 
-		ArrayList res = new ArrayList(length);
+		ArrayList<IBuildpathEntry> res = new ArrayList<IBuildpathEntry>(length);
 		for (int i = 0; i < length; ++i) {
 			Node node = list.item(i);
 
@@ -214,18 +258,18 @@
 							foundChildren);
 					IAccessRule[] accessRules = BuildpathEntry
 							.decodeAccessRules(attributeList);
-					IBuildpathEntry entry = DLTKCore.newLibraryEntry(Path
-							.fromPortableString(path), accessRules,
+					IBuildpathEntry entry = DLTKCore.newLibraryEntry(
+							Path.fromPortableString(path), accessRules,
 							extraAttributes, false, true);
 					res.add(entry);
 				}
 			}
 		}
 
-		IBuildpathEntry[] entries = (IBuildpathEntry[]) res
+		IBuildpathEntry[] entries = res
 				.toArray(new IBuildpathEntry[res.size()]);
 
-		return new UserLibrary(entries, isSystem);
+		return new UserLibrary(entries, isSystem, attributes);
 	}
 
 	public String toString() {
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibraryManager.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibraryManager.java
index 7f86edd..60f4b55 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibraryManager.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/UserLibraryManager.java
@@ -57,8 +57,9 @@
 				toolkit));
 	}
 
-	public static String makeLibraryName(String libName, IDLTKLanguageToolkit toolkit) {
-		if( toolkit == null ) {
+	public static String makeLibraryName(String libName,
+			IDLTKLanguageToolkit toolkit) {
+		if (toolkit == null) {
 			return "#" + libName; //$NON-NLS-1$
 		}
 		return toolkit.getNatureId() + "#" + libName; //$NON-NLS-1$
@@ -131,9 +132,9 @@
 						library = UserLibrary.createFromString(reader);
 					} catch (IOException e) {
 						Util
-								.log(
-										e,
-										"Exception while initializing user library " + libName); //$NON-NLS-1$
+						.log(
+								e,
+								"Exception while initializing user library " + libName); //$NON-NLS-1$
 						instancePreferences.remove(propertyName);
 						preferencesNeedFlush = true;
 						continue;
@@ -231,13 +232,19 @@
 	public synchronized void setUserLibrary(String libName,
 			IBuildpathEntry[] entries, boolean isSystemLibrary,
 			IDLTKLanguageToolkit toolkit) {
+		setUserLibrary(libName, entries, isSystemLibrary, null, toolkit);
+	}
+
+	public synchronized void setUserLibrary(String libName,
+			IBuildpathEntry[] entries, boolean isSystemLibrary,
+			Map<String, String> attributes, IDLTKLanguageToolkit toolkit) {
 		IEclipsePreferences instancePreferences = ModelManager
 				.getModelManager().getInstancePreferences();
 		String propertyName = BP_USERLIBRARY_PREFERENCES_PREFIX
 				+ makeLibraryName(libName, toolkit);
 		try {
 			String propertyValue = UserLibrary.serialize(entries,
-					isSystemLibrary);
+					isSystemLibrary, attributes);
 			instancePreferences.put(propertyName, propertyValue); // sends out
 			// a
 			// PreferenceChangeEvent
diff --git a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/internal/ui/wizards/buildpath/BPUserLibraryElement.java b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/internal/ui/wizards/buildpath/BPUserLibraryElement.java
index b54ee55..3736086 100644
--- a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/internal/ui/wizards/buildpath/BPUserLibraryElement.java
+++ b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/internal/ui/wizards/buildpath/BPUserLibraryElement.java
@@ -12,6 +12,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
@@ -50,9 +51,15 @@
 	private String fName;
 	private List fChildren;
 	private boolean fIsSystemLibrary;
+	private Map<String, String> fAttributes;
 
 	public BPUserLibraryElement(String name, IBuildpathContainer container,
 			IScriptProject project) {
+		this(name, container, project, null);
+	}
+
+	public BPUserLibraryElement(String name, IBuildpathContainer container,
+			IScriptProject project, Map<String, String> attributes) {
 		fName = name;
 		fChildren = new ArrayList();
 		if (container != null) {
@@ -72,10 +79,16 @@
 		} else {
 			fIsSystemLibrary = false;
 		}
+		fAttributes = attributes;
 	}
 
 	public BPUserLibraryElement(String name, boolean isSystemLibrary,
 			BPListElement[] children) {
+		this(name, isSystemLibrary, children, null);
+	}
+
+	public BPUserLibraryElement(String name, boolean isSystemLibrary,
+			BPListElement[] children, Map<String, String> attributes) {
 		fName = name;
 		fChildren = new ArrayList();
 		if (children != null) {
@@ -84,6 +97,7 @@
 			}
 		}
 		fIsSystemLibrary = isSystemLibrary;
+		fAttributes = attributes;
 	}
 
 	public BPListElement[] getChildren() {
@@ -103,6 +117,13 @@
 		return fIsSystemLibrary;
 	}
 
+	public String getAttribute(String name) {
+		if (fAttributes == null) {
+			return null;
+		}
+		return fAttributes.get(name);
+	}
+
 	public void add(BPListElement element) {
 		if (!fChildren.contains(element)) {
 			fChildren.add(element);
diff --git a/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/AllTests.java b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/AllTests.java
index 53b2cd4..ff7f949 100644
--- a/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/AllTests.java
+++ b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/AllTests.java
@@ -45,6 +45,8 @@
 		suite.addTest(new TestSuite(BuildParticipantManagerTests.class));
 		suite.addTest(BuildpathTests.suite());
 
+		suite.addTest(new TestSuite(UserLibraryTests.class));
+
 		suite.addTest(new TestSuite(CacheTests.class));
 
 		suite.addTest(new TestSuite(CompilerUtilTests.class));
diff --git a/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/UserLibraryTests.java b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/UserLibraryTests.java
new file mode 100644
index 0000000..f244dec
--- /dev/null
+++ b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/UserLibraryTests.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Zend Techologies Ltd.
+ * 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Zend Technologies Ltd. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.dltk.core.tests;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.dltk.core.DLTKCore;
+import org.eclipse.dltk.core.IBuildpathEntry;
+import org.eclipse.dltk.core.environment.EnvironmentManager;
+import org.eclipse.dltk.core.environment.EnvironmentPathUtils;
+import org.eclipse.dltk.internal.core.UserLibrary;
+
+public class UserLibraryTests extends TestCase {
+
+	private static String TEMPLATE = "";
+	static {
+		TEMPLATE += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+		TEMPLATE += "<userlibrary {2}systemlibrary=\"{1}\" version=\"1\">\n";
+		TEMPLATE += "	<archive path=\"org.eclipse.dltk.core.environment.localEnvironment/:{0}\"/>\n";
+		TEMPLATE += "</userlibrary>\n";
+	}
+
+	public void testSerializationSystemLibrary() throws IOException {
+		String path = "/segment01";
+
+		IBuildpathEntry[] entries = new IBuildpathEntry[] { createBuildpathEntry(path) };
+
+		String output = UserLibrary.serialize(entries, true);
+
+		assertEquals(output, createXML(path, true));
+	}
+
+	public void testSerializationNotSystemLibrary() throws IOException {
+		String path = "/segment01/segment02";
+
+		IBuildpathEntry[] entries = new IBuildpathEntry[] { createBuildpathEntry(path) };
+
+		String output = UserLibrary.serialize(entries, false);
+
+		assertEquals(output, createXML(path, false));
+	}
+
+	public void testSerializationAttributes() throws IOException {
+		String path = "/segment01/segment02";
+
+		IBuildpathEntry[] entries = new IBuildpathEntry[] { createBuildpathEntry(path) };
+
+		Map<String, String> attributes = new HashMap<String, String>();
+		attributes.put("libraryVersion", "1.0.0");
+		attributes.put("builtin", "true");
+		String output = UserLibrary.serialize(entries, false, attributes);
+
+		String attributesOutput = "__attribute__builtin=\"true\" ";
+		attributesOutput += "__attribute__libraryVersion=\"1.0.0\" ";
+		assertEquals(output, createXML(path, false, attributesOutput));
+	}
+
+	public void testDeserializationSystemLibrary() throws IOException {
+		String path = "/library/path";
+		IBuildpathEntry buildpathEntry = createBuildpathEntry(path);
+		String xml = createXML(path, true);
+		StringReader reader = new StringReader(xml);
+		UserLibrary userLibrary = UserLibrary.createFromString(reader);
+
+		assertEquals(userLibrary.getEntries().length, 1);
+		assertEquals(userLibrary.getEntries()[0], buildpathEntry);
+		assertEquals(userLibrary.isSystemLibrary(), true);
+		assertEquals(userLibrary.getAttributes().size(), 0);
+	}
+
+	public void testDeserializationNotSystemLibrary() throws IOException {
+		String path = "/library/path";
+		IBuildpathEntry buildpathEntry = createBuildpathEntry(path);
+		String xml = createXML(path, false);
+		StringReader reader = new StringReader(xml);
+		UserLibrary userLibrary = UserLibrary.createFromString(reader);
+
+		assertEquals(userLibrary.getEntries().length, 1);
+		assertEquals(userLibrary.getEntries()[0], buildpathEntry);
+		assertEquals(userLibrary.isSystemLibrary(), false);
+		assertEquals(userLibrary.getAttributes().size(), 0);
+	}
+
+	public void testDeserializationAttributes() throws IOException {
+		String path = "/library/path";
+		IBuildpathEntry buildpathEntry = createBuildpathEntry(path);
+
+		String attributesOutput = "__attribute__builtin=\"true\" ";
+		attributesOutput += "__attribute__libraryVersion=\"1.0.0\" ";
+		String xml = createXML(path, false, attributesOutput);
+		StringReader reader = new StringReader(xml);
+		UserLibrary userLibrary = UserLibrary.createFromString(reader);
+
+		Map<String, String> attributes = new HashMap<String, String>();
+		attributes.put("libraryVersion", "1.0.0");
+		attributes.put("builtin", "true");
+
+		assertEquals(userLibrary.getEntries().length, 1);
+		assertEquals(userLibrary.getEntries()[0], buildpathEntry);
+		assertEquals(userLibrary.isSystemLibrary(), false);
+		assertEquals(userLibrary.getAttributes(), attributes);
+	}
+
+	private IBuildpathEntry createBuildpathEntry(String path) {
+		return DLTKCore.newLibraryEntry(EnvironmentPathUtils.getFullPath(
+				EnvironmentManager.getLocalEnvironment(), new Path(path)),
+				false, true);
+	}
+
+	private String createXML(String path, boolean isSystemLibrary) {
+		return MessageFormat.format(TEMPLATE, path, isSystemLibrary, "");
+	}
+
+	private String createXML(String path, boolean isSystemLibrary,
+			String attributes) {
+		return MessageFormat
+				.format(TEMPLATE, path, isSystemLibrary, attributes);
+	}
+
+}