/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *     Frits Jalvingh - Contribution for Bug 459831 - [launching] Support attaching
 *     	external annotations to a JRE container
 *******************************************************************************/
package org.eclipse.jdt.internal.launching;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.jdt.launching.AbstractVMInstall;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMInstall2;
import org.eclipse.jdt.launching.IVMInstallType;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.LibraryLocation;
import org.eclipse.jdt.launching.VMStandin;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * This is a container for VM definitions such as the VM definitions that are
 * stored in the workbench preferences.
 * <p>
 * An instance of this class may be obtained from an XML document by calling
 * <code>parseXMLIntoContainer</code>.
 * </p>
 * <p>
 * An instance of this class may be translated into an XML document by calling
 * <code>getAsXML</code>.
 * </p>
 * <p>
 * Clients may instantiate this class; it is not intended to be subclassed.
 * </p>
 *
 * @since 2.1
 */
public class VMDefinitionsContainer {

	/**
	 * Map of VMInstallTypes to Lists of corresponding VMInstalls.
	 */
	private Map<IVMInstallType, List<IVMInstall>> fVMTypeToVMMap;

	/**
	 * Cached list of VMs in this container
	 */
	private List<IVMInstall> fVMList;

	/**
	 * VMs managed by this container whose install locations don't actually exist.
	 */
	private List<IVMInstall> fInvalidVMList;

	/**
	 * The composite identifier of the default VM.  This consists of the install type ID
	 * plus an ID for the VM.
	 */
	private String fDefaultVMInstallCompositeID;

	/**
	 * The identifier of the connector to use for the default VM.
	 */
	private String fDefaultVMInstallConnectorTypeID;

	/**
	 * Contains any error/information status of parsing XML
	 */
	private MultiStatus fStatus;

	/**
	 * Constructs an empty VM container
	 */
	public VMDefinitionsContainer() {
		fVMTypeToVMMap = new HashMap<>(10);
		fInvalidVMList = new ArrayList<>(10);
		fVMList = new ArrayList<>(10);
	}

	/**
	 * Add the specified VM to the VM definitions managed by this container.
	 * <p>
	 * If distinguishing valid from invalid VMs is important, the specified VM must
	 * have already had its install location set.  An invalid VM is one whose install
	 * location doesn't exist.
	 * </p>
	 *
	 * @param vm the VM to be added to this container
	 */
	public void addVM(IVMInstall vm) {
		if (!fVMList.contains(vm)) {
			IVMInstallType vmInstallType = vm.getVMInstallType();
			List<IVMInstall> vmList = fVMTypeToVMMap.get(vmInstallType);
			if (vmList == null) {
				vmList = new ArrayList<>(3);
				fVMTypeToVMMap.put(vmInstallType, vmList);
			}
			vmList.add(vm);
			File installLocation = vm.getInstallLocation();
			if (installLocation == null || vmInstallType.validateInstallLocation(installLocation).getSeverity() == IStatus.ERROR) {
				fInvalidVMList.add(vm);
			}
			fVMList.add(vm);
		}
	}

	/**
	 * Add all VM's in the specified list to the VM definitions managed by this container.
	 * <p>
	 * If distinguishing valid from invalid VMs is important, the specified VMs must
	 * have already had their install locations set.  An invalid VM is one whose install
	 * location doesn't exist.
	 * </p>
	 *
	 * @param vmList a list of VMs to be added to this container
	 */
	public void addVMList(List<IVMInstall> vmList) {
		Iterator<IVMInstall> iterator = vmList.iterator();
		while (iterator.hasNext()) {
			addVM(iterator.next());
		}
	}

	/**
	 * Return a mapping of VM install types to lists of VMs.  The keys of this map are instances of
	 * <code>IVMInstallType</code>.  The values are instances of <code>java.util.List</code>
	 * which contain instances of <code>IVMInstall</code>.
	 *
	 * @return Map the mapping of VM install types to lists of VMs
	 */
	public Map<IVMInstallType, List<IVMInstall>> getVMTypeToVMMap() {
		return fVMTypeToVMMap;
	}

	/**
	 * Return a list of all VMs in this container, including any invalid VMs.  An invalid
	 * VM is one whose install location does not exist on the file system.
	 * The order of the list is not specified.
	 *
	 * @return List the data structure containing all VMs managed by this container
	 */
	public List<IVMInstall> getVMList() {
		return fVMList;
	}

	/**
	 * Return a list of all valid VMs in this container.  A valid VM is one whose install
	 * location exists on the file system.  The order of the list is not specified.
	 *
	 * @return List
	 */
	public List<IVMInstall> getValidVMList() {
		List<IVMInstall> vms = getVMList();
		List<IVMInstall> resultList = new ArrayList<>(vms.size());
		resultList.addAll(vms);
		resultList.removeAll(fInvalidVMList);
		return resultList;
	}

	/**
	 * Returns the composite ID for the default VM.  The composite ID consists
	 * of an ID for the VM install type together with an ID for VM.  This is
	 * necessary because VM ids by themselves are not necessarily unique across
	 * VM install types.
	 *
	 * @return String returns the composite ID of the current default VM
	 */
	public String getDefaultVMInstallCompositeID(){
		return fDefaultVMInstallCompositeID;
	}

	/**
	 * Sets the composite ID for the default VM.  The composite ID consists
	 * of an ID for the VM install type together with an ID for VM.  This is
	 * necessary because VM ids by themselves are not necessarily unique across
	 * VM install types.
	 *
	 * @param id identifies the new default VM using a composite ID
	 */
	public void setDefaultVMInstallCompositeID(String id){
		fDefaultVMInstallCompositeID = id;
	}

	/**
	 * Return the default VM's connector type ID.
	 *
	 * @return String the current value of the default VM's connector type ID
	 */
	public String getDefaultVMInstallConnectorTypeID() {
		return fDefaultVMInstallConnectorTypeID;
	}

	/**
	 * Set the default VM's connector type ID.
	 *
	 * @param id the new value of the default VM's connector type ID
	 */
	public void  setDefaultVMInstallConnectorTypeID(String id){
		fDefaultVMInstallConnectorTypeID = id;
	}

	/**
	 * Return the VM definitions contained in this object as a String of XML.  The String
	 * is suitable for storing in the workbench preferences.
	 * <p>
	 * The resulting XML is compatible with the static method <code>parseXMLIntoContainer</code>.
	 * </p>
	 * @return String the results of flattening this object into XML
	 * @throws CoreException if serialization of the XML document failed
	 */
	public String getAsXML() throws CoreException {

		// Create the Document and the top-level node
		Document doc = DebugPlugin.newDocument();
		Element config = doc.createElement("vmSettings");    //$NON-NLS-1$
		doc.appendChild(config);

		// Set the defaultVM attribute on the top-level node
		if (getDefaultVMInstallCompositeID() != null) {
			config.setAttribute("defaultVM", getDefaultVMInstallCompositeID()); //$NON-NLS-1$
		}

		// Set the defaultVMConnector attribute on the top-level node
		if (getDefaultVMInstallConnectorTypeID() != null) {
			config.setAttribute("defaultVMConnector", getDefaultVMInstallConnectorTypeID()); //$NON-NLS-1$
		}

		// Create a node for each install type represented in this container
		Set<IVMInstallType> vmInstallTypeSet = getVMTypeToVMMap().keySet();
		Iterator<IVMInstallType> keyIterator = vmInstallTypeSet.iterator();
		while (keyIterator.hasNext()) {
			IVMInstallType vmInstallType = keyIterator.next();
			Element vmTypeElement = vmTypeAsElement(doc, vmInstallType);
			config.appendChild(vmTypeElement);
		}

		// Serialize the Document and return the resulting String
		return DebugPlugin.serializeDocument(doc);
	}

	/**
	 * Create and return a node for the specified VM install type in the specified Document.
	 *
	 * @param doc the backing {@link Document}
	 * @param vmType the {@link IVMInstallType} to create an {@link Element} for
	 * @return the new {@link Element}
	 */
	private Element vmTypeAsElement(Document doc, IVMInstallType vmType) {

		// Create a node for the VM type and set its 'id' attribute
		Element element= doc.createElement("vmType");   //$NON-NLS-1$
		element.setAttribute("id", vmType.getId());     //$NON-NLS-1$

		// For each VM of the specified type, create a subordinate node for it
		List<IVMInstall> vmList = getVMTypeToVMMap().get(vmType);
		Iterator<IVMInstall> vmIterator = vmList.iterator();
		while (vmIterator.hasNext()) {
			IVMInstall vm = vmIterator.next();
			Element vmElement = vmAsElement(doc, vm);
			element.appendChild(vmElement);
		}

		return element;
	}

	/**
	 * Create and return a node for the specified VM in the specified Document.
	 *
	 * @param doc the backing {@link Document}
	 * @param vm the {@link IVMInstall} to create an {@link Element} for
	 * @return the new {@link Element} representing the given {@link IVMInstall}
	 */
	private Element vmAsElement(Document doc, IVMInstall vm) {

		// Create the node for the VM and set its 'id' & 'name' attributes
		Element element= doc.createElement("vm");        //$NON-NLS-1$
		element.setAttribute("id", vm.getId());	         //$NON-NLS-1$
		element.setAttribute("name", vm.getName());      //$NON-NLS-1$

		// Determine and set the 'path' attribute for the VM
		String installPath= "";                          //$NON-NLS-1$
		File installLocation= vm.getInstallLocation();
		if (installLocation != null) {
			installPath= installLocation.getAbsolutePath();
		}
		element.setAttribute("path", installPath);       //$NON-NLS-1$

		// If the 'libraryLocations' attribute is specified, create a node for it
		LibraryLocation[] libraryLocations= vm.getLibraryLocations();
		if (libraryLocations != null) {
			Element libLocationElement = libraryLocationsAsElement(doc, libraryLocations);
			element.appendChild(libLocationElement);
		}

		// Java doc location
		URL url = vm.getJavadocLocation();
		if (url != null) {
			element.setAttribute("javadocURL", url.toExternalForm()); //$NON-NLS-1$
		}

		if (vm instanceof IVMInstall2) {
			String vmArgs = ((IVMInstall2)vm).getVMArgs();
			if (vmArgs != null && vmArgs.length() > 0) {
				element.setAttribute("vmargs", vmArgs); //$NON-NLS-1$
			}
		} else {
			String[] vmArgs = vm.getVMArguments();
			if (vmArgs != null && vmArgs.length > 0) {
				StringBuilder buffer = new StringBuilder();
				for (int i = 0; i < vmArgs.length; i++) {
					buffer.append(vmArgs[i] + " "); //$NON-NLS-1$
				}
				element.setAttribute("vmargs", buffer.toString()); //$NON-NLS-1$
			}
		}

		// VM attributes
		if (vm instanceof AbstractVMInstall) {
			Map<String, String> attributes = ((AbstractVMInstall)vm).getAttributes();
			if (!attributes.isEmpty()) {
				Element attrElement = doc.createElement("attributeMap"); //$NON-NLS-1$
				Iterator<Entry<String, String>> iterator = attributes.entrySet().iterator();
				while (iterator.hasNext()) {
					Entry<String, String> entry = iterator.next();
					Element entryElement = doc.createElement("entry"); //$NON-NLS-1$
					entryElement.setAttribute("key", entry.getKey()); //$NON-NLS-1$
					entryElement.setAttribute("value", entry.getValue()); //$NON-NLS-1$
					attrElement.appendChild(entryElement);
				}
				element.appendChild(attrElement);
			}
		}

		return element;
	}

	/**
	 * Create and return a 'libraryLocations' node.  This node owns subordinate nodes that
	 * list individual library locations.
	 *
	 * @param doc the backing {@link Document}
	 * @param locations the array of {@link LibraryLocation}s to create an {@link Element} for
	 * @return the new {@link Element} for the given {@link LibraryLocation}s
	 */
	private static Element libraryLocationsAsElement(Document doc, LibraryLocation[] locations) {
		Element root = doc.createElement("libraryLocations");       //$NON-NLS-1$
		for (int i = 0; i < locations.length; i++) {
			Element element = doc.createElement("libraryLocation");  //$NON-NLS-1$
			element.setAttribute("jreJar", locations[i].getSystemLibraryPath().toString()); //$NON-NLS-1$
			element.setAttribute("jreSrc", locations[i].getSystemLibrarySourcePath().toString()); //$NON-NLS-1$
			IPath annotationsPath = locations[i].getExternalAnnotationsPath();
			if (null != annotationsPath && !annotationsPath.isEmpty()) {
				element.setAttribute("jreExternalAnns", annotationsPath.toString()); //$NON-NLS-1$
			}

			IPath packageRootPath = locations[i].getPackageRootPath();
            if (packageRootPath != null) {
                element.setAttribute("pkgRoot", packageRootPath.toString()); //$NON-NLS-1$
            }

			URL javadocURL= locations[i].getJavadocLocation();
			if (javadocURL != null) {
				element.setAttribute("jreJavadoc", javadocURL.toExternalForm()); //$NON-NLS-1$
			}
			URL indexURL = locations[i].getIndexLocation();
			if(indexURL != null) {
				element.setAttribute("jreIndex", indexURL.toExternalForm()); //$NON-NLS-1$
			}
			root.appendChild(element);
		}
		return root;
	}

	public static VMDefinitionsContainer parseXMLIntoContainer(InputStream inputStream) throws IOException {
		VMDefinitionsContainer container = new VMDefinitionsContainer();
		parseXMLIntoContainer(inputStream, container);
		return container;
	}

	/**
	 * Parse the VM definitions contained in the specified InputStream into the
	 * specified container.
	 * <p>
	 * The VMs in the returned container are instances of <code>VMStandin</code>.
	 * </p>
	 * <p>
	 * This method has no side-effects.  That is, no notifications are sent for VM adds,
	 * changes, deletes, and the workbench preferences are not affected.
	 * </p>
	 * <p>
	 * If the <code>getAsXML</code> method is called on the returned container object,
	 * the resulting XML will be semantically equivalent (though not necessarily syntactically equivalent) as
	 * the XML contained in <code>inputStream</code>.
	 * </p>
	 * @param inputStream the <code>InputStream</code> containing XML that declares a set of VMs and a default VM
	 * @param container the container to add the VM definitions to
	 * @throws IOException if this method fails. Reasons include:<ul>
	 * <li>the XML in <code>inputStream</code> was badly formatted</li>
	 * <li>the top-level node was not 'vmSettings'</li>
	 * </ul>
	 * @since 3.2
	 */
	public static void parseXMLIntoContainer(InputStream inputStream, VMDefinitionsContainer container) throws IOException {


		// Do the parsing and obtain the top-level node
		Element config= null;
		// Wrapper the stream for efficient parsing
		try (InputStream stream = new BufferedInputStream(inputStream)) {
			DocumentBuilder parser = XmlProcessorFactoryJdtDebug.createDocumentBuilderWithErrorOnDOCTYPE();
			parser.setErrorHandler(new DefaultHandler());
			config = parser.parse(new InputSource(stream)).getDocumentElement();
		} catch (SAXException e) {
			throw new IOException(LaunchingMessages.JavaRuntime_badFormat);
		} catch (ParserConfigurationException e) {
			throw new IOException(LaunchingMessages.JavaRuntime_badFormat);
		}

		// If the top-level node wasn't what we expected, bail out
		if (!config.getNodeName().equalsIgnoreCase("vmSettings")) { //$NON-NLS-1$
			throw new IOException(LaunchingMessages.JavaRuntime_badFormat);
		}

		// Populate the default VM-related fields
		container.setDefaultVMInstallCompositeID(config.getAttribute("defaultVM")); //$NON-NLS-1$
		container.setDefaultVMInstallConnectorTypeID(config.getAttribute("defaultVMConnector")); //$NON-NLS-1$

		// Traverse the parsed structure and populate the VMType to VM Map
		NodeList list = config.getChildNodes();
		int length = list.getLength();
		for (int i = 0; i < length; ++i) {
			Node node = list.item(i);
			short type = node.getNodeType();
			if (type == Node.ELEMENT_NODE) {
				Element vmTypeElement = (Element) node;
				if (vmTypeElement.getNodeName().equalsIgnoreCase("vmType")) { //$NON-NLS-1$
					populateVMTypes(vmTypeElement, container);
				}
			}
		}
	}

	/**
	 * For the specified VM type node, parse all subordinate VM definitions and add them
	 * to the specified container.
	 *
	 * @param vmTypeElement the {@link Element} to populate the {@link VMDefinitionsContainer} from
	 * @param container the {@link VMDefinitionsContainer} to populate from the {@link Element}
	 */
	private static void populateVMTypes(Element vmTypeElement, VMDefinitionsContainer container) {
		// Retrieve the 'id' attribute and the corresponding VM type object
		String id = vmTypeElement.getAttribute("id");         //$NON-NLS-1$
		IVMInstallType vmType= JavaRuntime.getVMInstallType(id);
		if (vmType != null) {
			// For each VM child node, populate the container with a subordinate node
			NodeList vmNodeList = vmTypeElement.getElementsByTagName("vm"); //$NON-NLS-1$
			for (int i = 0; i < vmNodeList.getLength(); ++i) {
				populateVMForType(vmType, (Element) vmNodeList.item(i), container);
			}
		} else {
			// status information for removed VMs (missing VM type)
			NodeList vmNodeList = vmTypeElement.getElementsByTagName("vm"); //$NON-NLS-1$
			for (int i = 0; i < vmNodeList.getLength(); ++i) {
				Element vmElement = (Element) vmNodeList.item(i);
				String installPath= vmElement.getAttribute("path"); //$NON-NLS-1$
				String name = vmElement.getAttribute("name"); //$NON-NLS-1$
				IStatus status = null;
				if (name != null) {
					status = new Status(IStatus.INFO, LaunchingPlugin.ID_PLUGIN,
							NLS.bind(LaunchingMessages.VMDefinitionsContainer_0, new String[]{name}));
				} else if (installPath != null) {
					status = new Status(IStatus.INFO, LaunchingPlugin.ID_PLUGIN,
							NLS.bind(LaunchingMessages.VMDefinitionsContainer_0, new String[]{installPath}));
				} else {
					status = new Status(IStatus.INFO, LaunchingPlugin.ID_PLUGIN,
							NLS.bind(LaunchingMessages.VMDefinitionsContainer_2, new String[]{id}));
				}
				container.addStatus(status);
			}
		}
	}

	/**
	 * Parse the specified VM node, create a VMStandin for it, and add this to the
	 * specified container.
	 *
	 * @param vmType VM type
	 * @param vmElement XML element
	 * @param container container to add VM to
	 */
	private static void populateVMForType(IVMInstallType vmType, Element vmElement, VMDefinitionsContainer container) {
		String id= vmElement.getAttribute("id"); //$NON-NLS-1$
		if (id != null) {

			// Retrieve the 'path' attribute.  If none, skip this node.
			String installPath= vmElement.getAttribute("path"); //$NON-NLS-1$
			String name = vmElement.getAttribute("name"); //$NON-NLS-1$
			if (name == null) {
				if (installPath == null) {
					container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
							NLS.bind(LaunchingMessages.VMDefinitionsContainer_3, new String[]{vmType.getName()})));
					return;
				}
				container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, NLS.bind(LaunchingMessages.VMDefinitionsContainer_4, new String[]{installPath})));
				return;
			}
			if (installPath == null) {
				container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
						NLS.bind(LaunchingMessages.VMDefinitionsContainer_5, new String[]{name})));
				return;
			}

			// Create a VMStandin for the node and set its 'name' & 'installLocation' attributes
			VMStandin vmStandin = new VMStandin(vmType, id);
			vmStandin.setName(name);
			File installLocation= new File(installPath);
			vmStandin.setInstallLocation(installLocation);
			String install = installLocation.getAbsolutePath();
			//only consider a VM changed it is a standard VM
			boolean changed = StandardVMType.ID_STANDARD_VM_TYPE.equals(vmType.getId()) &&
										LaunchingPlugin.timeStampChanged(install);
			container.addVM(vmStandin);

			// Look for subordinate nodes.  These may be 'libraryLocation',
			// 'libraryLocations' or 'versionInfo'.
			if(!changed) {
				NodeList list = vmElement.getChildNodes();
				int length = list.getLength();
				for (int i = 0; i < length; ++i) {
					Node node = list.item(i);
					short type = node.getNodeType();
					if (type == Node.ELEMENT_NODE) {
						Element subElement = (Element)node;
						String subElementName = subElement.getNodeName();
						if (subElementName.equals("libraryLocation")) { //$NON-NLS-1$
							LibraryLocation loc = getLibraryLocation(subElement);
						vmStandin.setLibraryLocations(new LibraryLocation[]{loc});
						} else if (subElementName.equals("libraryLocations")) { //$NON-NLS-1$
							setLibraryLocations(vmStandin, subElement);
						} else if (subElementName.equals("attributeMap")) { //$NON-NLS-1$
							NodeList entries = subElement.getElementsByTagName("entry"); //$NON-NLS-1$
							for (int j = 0; j < entries.getLength(); j++) {
								Node entryNode = entries.item(j);
								if (entryNode instanceof Element) {
									Element entryElement = (Element) entryNode;
									String key = entryElement.getAttribute("key"); //$NON-NLS-1$
									String value = entryElement.getAttribute("value"); //$NON-NLS-1$
									if (key != null && value != null) {
										vmStandin.setAttribute(key, value);
									}
								}
							}
						}
					}

					// javadoc URL
					String externalForm = vmElement.getAttribute("javadocURL"); //$NON-NLS-1$
					if (externalForm != null && externalForm.length() > 0) {
						try {
							vmStandin.setJavadocLocation(new URL(externalForm));
						} catch (MalformedURLException e) {
							container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
									NLS.bind(LaunchingMessages.VMDefinitionsContainer_6, new String[]{name}), e));
						}
					}
				}
			}
			// VM Arguments
			String vmArgs = vmElement.getAttribute("vmargs"); //$NON-NLS-1$
			if (vmArgs != null && vmArgs.length() >0) {
				vmStandin.setVMArgs(vmArgs);
			}
		} else {
			String installPath= vmElement.getAttribute("path"); //$NON-NLS-1$
			String name = vmElement.getAttribute("name"); //$NON-NLS-1$
			if (name != null) {
				container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
						NLS.bind(LaunchingMessages.VMDefinitionsContainer_7, new String[]{name})));
			} else if (installPath != null) {
				container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
						NLS.bind(LaunchingMessages.VMDefinitionsContainer_7, new String[]{installPath})));
			} else {
				container.addStatus(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN,
					NLS.bind(LaunchingMessages.VMDefinitionsContainer_9, new String[]{vmType.getName()})));
			}
		}
	}

	/**
	 * Create & return a LibraryLocation object populated from the attribute values
	 * in the specified node.
	 *
	 * @param libLocationElement the {@link Element} to parse the {@link LibraryLocation} from
	 * @return the new {@link LibraryLocation} or <code>null</code> if the {@link Element} was malformed
	 */
	private static LibraryLocation getLibraryLocation(Element libLocationElement) {
		String jreJar= libLocationElement.getAttribute("jreJar"); //$NON-NLS-1$
		String jreSrc= libLocationElement.getAttribute("jreSrc"); //$NON-NLS-1$
		String pkgRoot= libLocationElement.getAttribute("pkgRoot"); //$NON-NLS-1$
		String jreJavadoc= libLocationElement.getAttribute("jreJavadoc"); //$NON-NLS-1$
		String jreIndex= libLocationElement.getAttribute("jreIndex"); //$NON-NLS-1$
		String externalAnns = libLocationElement.getAttribute("jreExternalAnns"); //$NON-NLS-1$
		// javadoc URL
		URL javadocURL= null;
		if (jreJavadoc.length() == 0) {
			jreJavadoc= null;
		} else {
			try {
				javadocURL= new URL(jreJavadoc);
			} catch (MalformedURLException e) {
				LaunchingPlugin.log("Library location javadoc element is specified incorrectly.");  //$NON-NLS-1$
			}
		}
		// index URL
		URL indexURL= null;
		if (jreIndex.length() == 0) {
			jreIndex= null;
		} else {
			try {
				indexURL= new URL(jreIndex);
			} catch (MalformedURLException e) {
				LaunchingPlugin.log("Library location jre index element is specified incorrectly.");  //$NON-NLS-1$
			}
		}
		if (jreJar != null && jreSrc != null && pkgRoot != null) {
			return new LibraryLocation(new Path(jreJar), new Path(jreSrc), new Path(pkgRoot), javadocURL, indexURL
					, externalAnns == null ? null : new Path(externalAnns));
		}
		LaunchingPlugin.log("Library location element is specified incorrectly.");  //$NON-NLS-1$
		return null;
	}

	/**
	 * Set the LibraryLocations on the specified VM, by extracting the subordinate
	 * nodes from the specified 'lirbaryLocations' node.
	 *
	 * @param vm the {@link IVMInstall} to populate from the given {@link Element}
	 * @param libLocationsElement the {@link Element} to populate the {@link IVMInstall} with
	 */
	private static void setLibraryLocations(IVMInstall vm, Element libLocationsElement) {
		NodeList list = libLocationsElement.getChildNodes();
		int length = list.getLength();
		List<LibraryLocation> locations = new ArrayList<>(length);
		for (int i = 0; i < length; ++i) {
			Node node = list.item(i);
			short type = node.getNodeType();
			if (type == Node.ELEMENT_NODE) {
				Element libraryLocationElement= (Element)node;
				if (libraryLocationElement.getNodeName().equals("libraryLocation")) { //$NON-NLS-1$
					locations.add(getLibraryLocation(libraryLocationElement));
				}
			}
		}
		vm.setLibraryLocations(locations.toArray(new LibraryLocation[locations.size()]));
	}

	/**
	 * Removes the VM from this container.
	 *
	 * @param vm VM install
	 */
	public void removeVM(IVMInstall vm) {
		fVMList.remove(vm);
		fInvalidVMList.remove(vm);
		List<IVMInstall> list = fVMTypeToVMMap.get(vm.getVMInstallType());
		if (list != null) {
			list.remove(vm);
		}
	}

	private void addStatus(IStatus status) {
		if (fStatus == null) {
			fStatus = new MultiStatus(LaunchingPlugin.ID_PLUGIN, 0, LaunchingMessages.VMDefinitionsContainer_10, null);
		}
		fStatus.add(status);
	}

	/**
	 * Returns status from parsing VM installs or <code>null</code> if none.
	 *
	 * @return status or <code>null</code>
	 */
	public IStatus getStatus() {
		return fStatus;
	}
}
