/*******************************************************************************
 * Copyright (c) 2007, 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
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.update;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.xml.parsers.*;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

/**
 * Parser for platform.xml files. 
 * 
 * @since 1.0
 */
public class ConfigurationParser implements ConfigurationConstants {
	static final String PLATFORM_BASE = "platform:/base/"; //$NON-NLS-1$
	private URL osgiInstallArea;

	/*
	 * Parse the given file handle which points to a platform.xml file and a configuration object.
	 * Returns null if the file doesn't exist.
	 */
	static Configuration parse(File file, URL osgiInstallArea) throws ProvisionException {
		return new ConfigurationParser(osgiInstallArea).internalParse(file);
	}

	private ConfigurationParser(URL osgiInstallArea) {
		this.osgiInstallArea = osgiInstallArea;
	}

	/*
	 * Create a feature object based on the given DOM node. 
	 * Return the new feature.
	 */
	private Feature createFeature(Node node, Site site) {
		Feature result = new Feature(site);
		String id = getAttribute(node, ATTRIBUTE_ID);
		if (id != null)
			result.setId(id);
		String url = getAttribute(node, ATTRIBUTE_URL);
		if (url != null)
			result.setUrl(url);
		String version = getAttribute(node, ATTRIBUTE_VERSION);
		if (version != null)
			result.setVersion(version);
		String pluginIdentifier = getAttribute(node, ATTRIBUTE_PLUGIN_IDENTIFIER);
		if (pluginIdentifier != null)
			result.setPluginIdentifier(pluginIdentifier);
		String pluginVersion = getAttribute(node, ATTRIBUTE_PLUGIN_VERSION);
		// plug-in version is the same as the feature version if it is missing
		if (pluginVersion == null)
			pluginVersion = version;
		if (pluginVersion != null)
			result.setPluginVersion(pluginVersion);
		String application = getAttribute(node, ATTRIBUTE_APPLICATION);
		if (application != null)
			result.setApplication(application);

		// get primary flag
		String flag = getAttribute(node, ATTRIBUTE_PRIMARY);
		if (flag != null && Boolean.valueOf(flag).booleanValue())
			result.setPrimary(true);

		// get install locations
		String locations = getAttribute(node, ATTRIBUTE_ROOT);
		if (locations != null) {
			StringTokenizer tokenizer = new StringTokenizer(locations, ","); //$NON-NLS-1$
			ArrayList<URL> rootList = new ArrayList<URL>();
			while (tokenizer.hasMoreTokens()) {
				try {
					URL rootEntry = new URL(tokenizer.nextToken().trim());
					rootList.add(rootEntry);
				} catch (MalformedURLException e) {
					// skip bad entries ...
				}
			}
			URL[] roots = rootList.toArray(new URL[rootList.size()]);
			result.setRoots(roots);
		}

		return result;
	}

	/*
	 * Create the features from the given DOM node.
	 */
	private void createFeatures(Node node, Site site) {
		NodeList children = node.getChildNodes();
		int size = children.getLength();
		for (int i = 0; i < size; i++) {
			Node child = children.item(i);
			if (child.getNodeType() != Node.ELEMENT_NODE)
				continue;
			if (!ELEMENT_FEATURE.equalsIgnoreCase(child.getNodeName()))
				continue;
			Feature feature = createFeature(child, site);
			if (feature != null)
				site.addFeature(feature);
		}
	}

	/*
	 * Create a site based on the given DOM node.
	 */
	private Site createSite(Node node) {
		Site result = new Site();
		String policy = getAttribute(node, ATTRIBUTE_POLICY);
		if (policy != null)
			result.setPolicy(policy);
		String enabled = getAttribute(node, ATTRIBUTE_ENABLED);
		if (enabled != null)
			result.setEnabled(Boolean.valueOf(enabled).booleanValue());
		String updateable = getAttribute(node, ATTRIBUTE_UPDATEABLE);
		if (updateable != null)
			result.setUpdateable(Boolean.valueOf(updateable).booleanValue());
		String url = getAttribute(node, ATTRIBUTE_URL);
		if (url != null) {
			try {
				// do this to ensure the location is an encoded URI
				URI uri = URIUtil.fromString(url);
				URI osgiURI = osgiInstallArea != null ? URIUtil.toURI(osgiInstallArea) : null;
				result.setUrl(getLocation(uri, osgiURI).toString());
			} catch (URISyntaxException e) {
				result.setUrl(url);
			}
		}
		String linkFile = getAttribute(node, ATTRIBUTE_LINKFILE);
		if (linkFile != null)
			result.setLinkFile(linkFile);
		String list = getAttribute(node, ATTRIBUTE_LIST);
		if (list != null)
			for (StringTokenizer tokenizer = new StringTokenizer(list, ","); tokenizer.hasMoreTokens();) //$NON-NLS-1$
				result.addPlugin(tokenizer.nextToken());
		createFeatures(node, result);
		return result;
	}

	/*
	 * Convert the given url string to an absolute url. If the string is 
	 * platform:/base/ then return a string which represents the osgi
	 * install area.
	 */
	private URI getLocation(URI location, URI osgiArea) {
		if (osgiArea == null)
			return location;
		if (PLATFORM_BASE.equals(location.toString()))
			return osgiArea;
		return URIUtil.makeAbsolute(location, osgiArea);
	}

	/*
	 * Return the attribute with the given name, or null if it does
	 * not exist.
	 */
	private String getAttribute(Node node, String name) {
		NamedNodeMap attributes = node.getAttributes();
		Node temp = attributes.getNamedItem(name);
		return temp == null ? null : temp.getNodeValue();
	}

	/*
	 * Load the given file into a DOM document.
	 */
	private Document load(InputStream input) throws ParserConfigurationException, IOException, SAXException {
		// load the feature xml
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		input = new BufferedInputStream(input);
		try {
			return builder.parse(input);
		} finally {
			if (input != null)
				try {
					input.close();
				} catch (IOException e) {
					// ignore
				}
		}
	}

	/*
	 * Parse the given file handle which points to a platform.xml file and a configuration object.
	 * Returns null if the file doesn't exist.
	 */
	private Configuration internalParse(File file) throws ProvisionException {
		if (!file.exists()) {
			// remove from cache since it doesn't exist anymore on disk
			ConfigurationCache.put(file, null);
			return null;
		}
		// have we read this before?
		Configuration result = ConfigurationCache.get(file);
		if (result != null)
			return result;
		try {
			InputStream input = new BufferedInputStream(new FileInputStream(file));
			Document document = load(input);
			result = process(document);
			// save for future use
			ConfigurationCache.put(file, result);
			return result;
		} catch (IOException e) {
			throw new ProvisionException(NLS.bind(Messages.error_reading_config, file), e);
		} catch (ParserConfigurationException e) {
			throw new ProvisionException(Messages.error_parsing_config, e);
		} catch (SAXException e) {
			throw new ProvisionException(Messages.error_parsing_config, e);
		}
	}

	/*
	 * Process the given DOM document and create the appropriate
	 * site objects.
	 */
	private Configuration process(Document document) {
		Node node = getConfigElement(document);
		if (node == null)
			return null;
		Configuration configuration = createConfiguration(node);
		NodeList children = node.getChildNodes();
		int size = children.getLength();
		for (int i = 0; i < size; i++) {
			Node child = children.item(i);
			if (child.getNodeType() != Node.ELEMENT_NODE)
				continue;
			if (!ELEMENT_SITE.equalsIgnoreCase(child.getNodeName()))
				continue;
			Site site = createSite(child);
			if (site != null)
				configuration.add(site);
		}
		return configuration;
	}

	private Configuration createConfiguration(Node node) {
		Configuration result = new Configuration();
		String value = getAttribute(node, ATTRIBUTE_DATE);
		if (value != null)
			result.setDate(value);
		value = getAttribute(node, ATTRIBUTE_TRANSIENT);
		if (value != null)
			result.setTransient(Boolean.valueOf(value).booleanValue());
		value = getAttribute(node, ATTRIBUTE_SHARED_UR);
		if (value != null)
			result.setSharedUR(value);
		value = getAttribute(node, ATTRIBUTE_VERSION);
		if (value != null)
			result.setVersion(value);
		return result;
	}

	private Node getConfigElement(Document doc) {
		NodeList children = doc.getChildNodes();
		int size = children.getLength();
		for (int i = 0; i < size; i++) {
			Node child = children.item(i);
			if (child.getNodeType() != Node.ELEMENT_NODE)
				continue;
			if (ELEMENT_CONFIG.equalsIgnoreCase(child.getNodeName()))
				return child;
		}
		return null;
	}
}
