--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/themeinstaller/source/src/com/nokia/tools/themeinstaller/installationmanager/ManifestFactory.java Tue Feb 02 00:15:44 2010 +0200
@@ -0,0 +1,593 @@
+/*
+ * 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;
+ }
+
+}