themeinstaller/source/src/com/nokia/tools/themeinstaller/installationmanager/ManifestFactory.java
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:32:13 +0100
branchRCL_3
changeset 18 04b7640f6fb5
parent 0 05da4621cfb2
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201032 Kit: 201035

/*
 * Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  Uses XML parser to read the manifest and created objects that
 *                represent the manifest.
 *
 */

package com.nokia.tools.themeinstaller.installationmanager;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.nokia.tools.themeinstaller.localisation.DTDReader;
import com.nokia.tools.themeinstaller.localisation.Localisation;
import com.nokia.tools.themeinstaller.localisation.LocalisationStore;
import com.nokia.tools.themeinstaller.odtconverter.ConverterProperties;
import com.nokia.tools.themeinstaller.odtconverter.IParseOperationListener;
import com.nokia.tools.themeinstaller.odtconverter.MimeTypeResolver;
import com.nokia.tools.themeinstaller.odtconverter.ThemeStatusResolver;
import com.nokia.tools.themeinstaller.xmlparser.XMLParser;

/**
 * Uses XML parser to read the manifest and created objects that represent the
 * manifest.
 */
public class ManifestFactory implements IParseOperationListener {

	// CONSTANTS
	// Manifest root node names
	private static final String MULTI_THEME = "datfiles";

	// Manifest element names
	private static final String APP_UID_ELEMENT = "AppUid";
	private static final String PROVIDER_UID_ELEMENT = "Provideruid";
	private static final String THEME_UID_ELEMENT = "ThemeUid";
	private static final String THEME_STATUS_ELEMENT = "ThemeStatus";
	private static final String THEME_FULL_NAME_ELEMENT = "ThemeFullName";
	private static final String THEME_SHORT_NAME_ELEMENT = "ThemeShortName";
	private static final String THEME_VERSION_ELEMENT = "ThemeVersion";
	private static final String FILE_XML_ELEMENT = "FileXML";
	private static final String FILE_CSS_ELEMENT = "FileCSS";
	private static final String FILE_DTD_ELEMENT = "FileDTD";
	private static final String FILE_DAT_ELEMENT = "FileDAT";
	private static final String FILE_RESOURCE_ELEMENT = "FileResource";
	private static final String CACHE_VALUE_MEMORY = "CacheMemory";
	private static final String LOCKING_ELEMENT = "Locking";
	private static final String LOCKED_VALUE = "Locked";
	private static final String CACHE_TYPE_ELEMENT = "CacheType";
	private static final String CACHE_VALUE_NONE = "CacheNone";
	private static final String CACHE_VALUE_FILE = "CacheFile";

	// Language element names
	private static final String LANGUAGE_SPECIFIC_ELEMENT = "LanguageSpecific";
	private static final int LANGUAGE_INDEPENDENT = 0;
	private static final int LANG_NOT_SET = -1;

	// Integer conversion radix
	private static final int RADIX = 16;

	// Screen size
	private static final int SCREEN_SIZE_X = 0;
	private static final int SCREEN_SIZE_Y = 0;

	// Resource file cache type
	public static int CACHE_TYPE_CACHE_NONE = 0;
	public static int CACHE_TYPE_CACHE_FILE = 1;
	public static int CACHE_TYPE_CACHE_MEMORY = 2;

	// Locking policy flag-definition from native, bit-masked.
	public static final int E_XN_UNLOCKED = 0x0000; // 0b0000000000000000,
	public static final int E_XN_LOCKED = 0x0001; // 0b0000000000000001,

	public static final String CURRENT_DIR = ".";

	// Property keys
	private static final String THEME_PROVIDER_KEY = "theme_provider_name";
	private static final String NAME_SPACE_KEY = "theme_name_space";

	// Lock for monitoring parse operation completions
	private Lock iLock;

	// Mime type resolver
	private MimeTypeResolver iMimeTypeResolver;

	// Converter properties
	private ConverterProperties iProperties;

	// Localisation settings
	private File iLocSettings;

	/**
	 * Constructor.
	 * 
	 * @param aLocSettings
	 *            Localisation settings
	 * @throws IOException
	 *             if converter properties can not be read
	 */
	public ManifestFactory(File aLocSettings) throws IOException {
		iLock = new Lock();
		iMimeTypeResolver = new MimeTypeResolver();
		iProperties = ConverterProperties.getInstance();
		iLocSettings = aLocSettings;
	}

	/**
	 * Parse a manifest and create the manifest object.
	 * 
	 * @param aFile
	 *            Manifest file
	 * @return Manifest instance
	 * @throws IOException
	 *             if the manifest can not be read
	 */
	public IThemeManifest createManifest(File aFile) throws IOException {
		// Read the manifest to the DOM document
		Document document = readManifest(aFile);
		IThemeManifest manifest = new ThemeManifest();

		// Set data directory that contains the data files of the theme
		if (aFile.getParent() == null) {
			manifest.setDataDir(CURRENT_DIR + File.separatorChar);
		} else {
			manifest.setDataDir(aFile.getParent() + File.separatorChar);
		}

		// Parse manifest contents
		String rootNodeName = document.getFirstChild().getNodeName();
		if (rootNodeName == MULTI_THEME) {
			// Multi theme manifest
			parseMultiThemeManifest(document, manifest);
		} else {
			// Single theme manifest
			parseManifest(document, manifest);
			parseLanguageSpecificData(document, manifest);
			parseResources(document, manifest, LANGUAGE_INDEPENDENT);
		}

		return manifest;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seecom.nokia.tools.themeinstaller.odtconverter.IParseOperationListener#
	 * parseOperationCompleted(int, java.lang.String)
	 */
	public void parseOperationCompleted(int aErr, String aReason) {
		iLock.unLock();
		if (aErr != 0) {
			throw new IllegalArgumentException(
					"Theme manifest parsing failed: " + aErr + ", " + aReason);
		}
	}

	/**
	 * Parse the manifest file.
	 * 
	 * @param aManifest
	 *            Manifest file
	 * @return DOM Document of the manifest
	 */
	private Document readManifest(File aManifest) {
		// Parse the manifest
		XMLParser parser = new XMLParser(aManifest.getPath());
		parser.addListener(this);

		try {
			parser.parse();
		} catch (Exception e) {
			throw new IllegalArgumentException(
					"Theme manifest parsing failed: " + e.getMessage());
		}

		// Wait for the operation completion
		iLock.lock();

		// Return the document that was formed
		return parser.getDOMDocument();
	}

	/**
	 * Parses a multi theme manifest from DOM to the manifest instance.
	 * 
	 * @param aDocument
	 *            DOM Document containing the manifest data (source)
	 * @param aManifest
	 *            Theme manifest (destination)
	 */
	private void parseMultiThemeManifest(Document aDocument,
			IThemeManifest aManifest) {
		// Add DAT file names
		NodeList nodes = aDocument.getElementsByTagName(FILE_DAT_ELEMENT);
		Node node = null;
		for (int i = 0; i < nodes.getLength(); i++) {
			node = nodes.item(i);
			aManifest.addManifestFile(aManifest.getDataDir()
					+ node.getTextContent());
		}
	}

	/**
	 * Parses a manifest from DOM to the manifest instance.
	 * 
	 * @param aDocument
	 *            DOM Document containing the manifest data (source)
	 * @param aManifest
	 *            Theme manifest (destination)
	 */
	private void parseManifest(Document aDocument, IThemeManifest aManifest) {
		// Set application uid
		NodeList nodes = aDocument.getElementsByTagName(APP_UID_ELEMENT);
		Node node = checkOneNode(nodes, APP_UID_ELEMENT);
		if (node != null)
			aManifest.setApplicationUid(Long.valueOf(node.getTextContent(),
					RADIX));

		// Set provider uid
		nodes = aDocument.getElementsByTagName(PROVIDER_UID_ELEMENT);
		node = checkOneNode(nodes, PROVIDER_UID_ELEMENT);
		if (node != null)
			aManifest
					.setProviderUid(Long.valueOf(node.getTextContent(), RADIX));

		// Set theme uid
		nodes = aDocument.getElementsByTagName(THEME_UID_ELEMENT);
		node = checkOneNode(nodes, THEME_UID_ELEMENT);
		if (node != null)
			aManifest.setThemeUid(Long.valueOf(node.getTextContent(), RADIX));

		// Set name space
		aManifest.setNameSpace(iProperties.getProperty(NAME_SPACE_KEY));

		// Set theme provider name
		aManifest.setProviderName(iProperties.getProperty(THEME_PROVIDER_KEY));

		// Set default theme full name before localization
		nodes = aDocument.getElementsByTagName(THEME_FULL_NAME_ELEMENT);
		if (nodes.getLength() > 0) {
			// Get first theme name that is found (for a default name).
			// The used name is specified in the language specific data.
			aManifest.setThemeFullName(nodes.item(0).getTextContent());
		}
		// else
		// {
		// throw new IllegalArgumentException(
		// "Syntax error in manifest file: theme full name not found" );
		// }

		// Set theme short name
		nodes = aDocument.getElementsByTagName(THEME_SHORT_NAME_ELEMENT);
		node = checkOneNode(nodes, THEME_SHORT_NAME_ELEMENT);
		if (node != null)
			aManifest.setThemeShortName(node.getTextContent());

		// Set theme version
		nodes = aDocument.getElementsByTagName(THEME_VERSION_ELEMENT);
		node = checkOneNode(nodes, THEME_VERSION_ELEMENT);
		if (node != null)
			aManifest.setThemeVersion(node.getTextContent());

		// Screen size, not used in Xuikon at the moment
		aManifest.setScreenSizeX(new Integer(SCREEN_SIZE_X));
		aManifest.setScreenSizeY(new Integer(SCREEN_SIZE_Y));

		// Resolve theme status
		nodes = aDocument.getElementsByTagName(THEME_STATUS_ELEMENT);
		int flags = ThemeStatusResolver.E_XN_THEME_STATUS_NONE;
		for (int i = 0; i < nodes.getLength(); i++) {
			node = nodes.item(i);
			flags |= ThemeStatusResolver.getValue(node.getTextContent())
					.intValue();
			if (node.getTextContent().equals(
					ThemeStatusResolver.THEME_STATUS_LICENCEE_RESTORABLE)) {
				// This theme is restored when licensee default theme is
				// restored.
				// When using this flag, the ThemeStatusLicenceeDefault-flag
				// must be activated.
				flags |= ThemeStatusResolver.E_XN_THEME_STATUS_LICENCEE_DEFAULT;
			}
		}
		aManifest.setThemeStatus(new Integer(flags));

		// Set XML file name
		NodeList files = aDocument.getElementsByTagName(FILE_XML_ELEMENT);
		node = checkOneNode(files, FILE_XML_ELEMENT);
		if (node != null)
			aManifest.setXMLFile(node.getTextContent());

		// Set CSS file name
		files = aDocument.getElementsByTagName(FILE_CSS_ELEMENT);
		node = checkMaxOneLanguageIndependentNode(files);
		if (node != null) {
			aManifest.setCSSFile(node.getTextContent());
		}

		// Set DTD file name
		files = aDocument.getElementsByTagName(FILE_DTD_ELEMENT);
		node = checkMaxOneLanguageIndependentNode(files);
		if (node != null) {
			aManifest.setDTDFile(node.getTextContent());
		}
	}

	/**
	 * Parses language specific data in the manifest.
	 * 
	 * @param aDocument
	 *            DOM Document containing the manifest data (source)
	 * @param aManifest
	 *            Theme manifest (destination)
	 * @throws IOException
	 *             if the manifest can not be parsed
	 */
	private void parseLanguageSpecificData(Document aDocument,
			IThemeManifest aManifest) throws IOException {
		// Get languages
		NodeList languages = aDocument
				.getElementsByTagName(LANGUAGE_SPECIFIC_ELEMENT);

		Node langSpecificNode = null;
		NamedNodeMap langAttr = null;
		NodeList langSpecificChildren = null;
		Node langSpecificChild = null;

		NodeList resources = aDocument
				.getElementsByTagName(FILE_RESOURCE_ELEMENT);
		Vector langResources = null;

		// Process all languages
		for (int i = 0; i < languages.getLength(); i++) {
			// Language specific data
			int langId = LANG_NOT_SET;
			String extDtd = null;
			String extCss = null;
			String themeFullName = null;

			// Take a LanguageSpecific node
			langSpecificNode = languages.item(i);

			// There should be only one language for each LanguageSpecific node
			langAttr = langSpecificNode.getAttributes();
			if (langAttr.getLength() == 1) {
				String langStr = langAttr.item(0).getNodeValue();
				langId = Integer.valueOf(langStr).intValue();
			}

			langSpecificChildren = langSpecificNode.getChildNodes();

			// Read language specific elements
			String nodeName = null;

			for (int j = 0; j < langSpecificChildren.getLength(); j++) {
				langSpecificChild = langSpecificChildren.item(j);
				nodeName = langSpecificChild.getNodeName();

				// Language specific DTD file name
				if (FILE_DTD_ELEMENT.equals(nodeName)) {
					extDtd = langSpecificChild.getTextContent();
				}
				// Language specific CSS
				else if (FILE_CSS_ELEMENT.equals(nodeName)) {
					extCss = langSpecificChild.getTextContent();
				}
				// Localized theme full name
				else if (THEME_FULL_NAME_ELEMENT.equals(nodeName)) {
					themeFullName = langSpecificChild.getTextContent();
				}
			}

			// Parse language specific resources
			langResources = parseResources(aManifest, resources, langId);

			// Verify that mandatory fields language id and DTD file were set
			if (langId == LANG_NOT_SET || extDtd == null) {
				throw new IllegalArgumentException(
						"Syntax error in language specifications of the manifest");
			}

			// Find the DTD file for reading the localised theme full name
			File dtd = null;
			if (iLocSettings != null) {
				// Use enhanced localisation: Find the DTD file
				LocalisationStore ls = LocalisationStore
						.getInstance(iLocSettings);
				Localisation l = ls.getLocalisation(aManifest
						.getApplicationUid().longValue(), aManifest
						.getProviderUid().longValue(), aManifest.getThemeUid()
						.longValue());
				dtd = l.findDTD(extDtd);
			} else {
				dtd = new File(aManifest.getDataDir() + extDtd);
			}

			// Resolve localised theme full name, if specified
			themeFullName = DTDReader.readEntity(dtd, themeFullName);

			// Create new language specific data and add it to the languages
			// list
			LanguageSpecificData language = new LanguageSpecificData(
					new Integer(langId), extDtd, extCss, themeFullName,
					langResources);
			aManifest.addLanguage(language);
		}

		// Unlocalized variant (causes the .o0000 file to be generated)
		LanguageSpecificData language = new LanguageSpecificData(new Integer(
				LANGUAGE_INDEPENDENT), null, null, null, null);
		aManifest.addLanguage(language);
	}

	/**
	 * Parses resource data in the manifest.
	 * 
	 * @param aDocument
	 *            DOM Document containing the manifest data (source)
	 * @param aManifest
	 *            Theme manifest (destination)
	 * @param aLanguageId
	 *            Id of the language of which resources will be parsed. On 0,
	 *            only language independent resources are parsed.
	 */
	private void parseResources(Document aDocument, IThemeManifest aManifest,
			int aLanguageId) {
		// Do the parsing operation
		NodeList resourceList = aDocument
				.getElementsByTagName(FILE_RESOURCE_ELEMENT);
		Vector resources = parseResources(aManifest, resourceList, aLanguageId);

		// Add resources to the manifest
		Enumeration e = resources.elements();
		while (e.hasMoreElements()) {
			aManifest.addResource((ThemeResource) e.nextElement());
		}
	}

	/**
	 * Parses resource files defined in the manifest. Resources must be parsed
	 * after the rest of the manifest is parsed. Theme status is used here for
	 * determining locking policies.
	 * 
	 * @param aManifest
	 *            Theme manifest
	 * @param aNodeList
	 *            Node list to parse
	 * @param aLanguageId
	 *            Id of the language of which resources will be parsed. On 0,
	 *            only language independent resources are parsed.
	 */
	private Vector parseResources(IThemeManifest aManifest, NodeList aNodeList,
			int aLanguageId) {
		Vector result = new Vector();
		Node node = null;

		// Browse through all resources in the list
		int count = aNodeList.getLength();
		for (int i = 0; i < count; i++) {
			node = aNodeList.item(i);
			String filename = node.getTextContent();
			NamedNodeMap attributes = node.getAttributes();

			int cacheType = CACHE_TYPE_CACHE_NONE;
			int lockingPolicy = E_XN_UNLOCKED;

			// Set cache type
			Node cacheTypeNode = attributes.getNamedItem(CACHE_TYPE_ELEMENT);
			if ((cacheTypeNode == null)
					|| cacheTypeNode.getTextContent().equals(CACHE_VALUE_NONE)) {
				cacheType = CACHE_TYPE_CACHE_NONE;
			} else if (cacheTypeNode.getTextContent().equals(CACHE_VALUE_FILE)) {
				cacheType = CACHE_TYPE_CACHE_FILE;
			} else if (cacheTypeNode.getTextContent()
					.equals(CACHE_VALUE_MEMORY)) {
				cacheType = CACHE_TYPE_CACHE_MEMORY;
			} else {
				throw new IllegalArgumentException(
						"Syntax error in the manifest, can not resolve resource cache type");
			}

			// Set locking policy
			Node locking = attributes.getNamedItem(LOCKING_ELEMENT);
			if (locking != null
					&& locking.getTextContent().equals(LOCKED_VALUE)) {
				lockingPolicy = E_XN_LOCKED;
			}

			// If EXnThemeStatusLicenceeDefault flag is set, locking policy is
			// E_XN_LOCKED
			if ((aManifest.getThemeStatus().intValue() & ThemeStatusResolver.E_XN_THEME_STATUS_LICENCEE_DEFAULT) != 0) {
				lockingPolicy = E_XN_LOCKED;
			}

			// Resolve mime type
			String mime = iMimeTypeResolver.getMimeType(filename);

			// Resolve resource type
			int resourceType = iMimeTypeResolver.getResourceType(filename);

			// Verify language id
			Node parent = node.getParentNode();
			NamedNodeMap attr = parent.getAttributes();

			// Language specific resources
			if (attr.getLength() == 1
					&& aLanguageId == Integer.valueOf(
							attr.item(0).getNodeValue()).intValue()) {
				ThemeResource resource = new ThemeResource(filename, cacheType,
						lockingPolicy, aManifest.getNameSpace(), resourceType,
						mime);
				result.add(resource);
			}
			// Language independent resources
			else if (aLanguageId == LANGUAGE_INDEPENDENT
					&& !LANGUAGE_SPECIFIC_ELEMENT.equals(parent.getNodeName())) {
				ThemeResource resource = new ThemeResource(filename, cacheType,
						lockingPolicy, aManifest.getNameSpace(), resourceType,
						mime);
				result.add(resource);
			}
		}

		return result;
	}

	/**
	 * Checks that there exists just one node in the list
	 * 
	 * @param aNodeList
	 *            Node list to be checked
	 * @return Only one existing node
	 * @throws IllegalArgumentException
	 *             If there is more or less than one node in list
	 */
	private Node checkOneNode(NodeList aNodeList, String tagName) {

		if (aNodeList.getLength() > 1) {
			 throw new IllegalArgumentException(
			 "Syntax error in manifest file, more child nodes than expected");
		}
		return aNodeList.item(0);
	}

	/**
	 * Checks that there exists max one node in the list and it is language
	 * independent.
	 * 
	 * @param aNodeList
	 *            Node list to be checked
	 * @return The existing language independent node or null
	 * @throws IllegalArgumentException
	 *             If there is more or less than one node in list
	 */
	private Node checkMaxOneLanguageIndependentNode(NodeList aNodeList) {
		Node result = null;
		int count = 0;
		for (int i = 0; i < aNodeList.getLength(); i++) {
			// Verify language in-dependency
			Node node = aNodeList.item(i);
			Node parent = node.getParentNode();
			if (!LANGUAGE_SPECIFIC_ELEMENT.equals(parent.getNodeName())) {
				result = node;
				count++;
			}
		}

		if (count > 1) {
			throw new IllegalArgumentException(
					"Syntax error in manifest file, more language "
							+ "independent child nodes than expected");
		}

		return result;
	}

}