#
# Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of the License "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: 
# raptor_cache module
# This module represents a cache of objects for a Raptor program.
#

import raptor_data
import raptor_xml
import generic_path
import os

# raptor_cache module attributes


# raptor_cache module classes

class Cache:

	def __init__(self, Raptor):
		self.raptor = Raptor
		self.aliases = {}
		self.groups = {}
		self.interfaces = {}
		self.variants = {}

	def Load(self, gPathOrGPathList, cacheID = ""):
		"""scan directory for xml files containing Raptor objects.
		Input is either a generic path object or a list of
		generic path objects.

		If a cacheID is supplied then the loaded objects are
		placed into containers which are marked with that ID.
		This is useful for Interfaces which may be loaded from
		several locations (different epoc32 trees for example)
		but need to be kept separate.
		"""

		pathlist = []
		filenames = []

		# Create pathlist - this will be of length one if gPathOrGPathList is a
		# generic path object; otherwise it's a list so just make all supplied paths generic
		if isinstance(gPathOrGPathList, list):
			pathlist = map(lambda x: x.GetLocalString(), gPathOrGPathList)
		elif isinstance(gPathOrGPathList, generic_path.Path):
			pathlist = [gPathOrGPathList.GetLocalString()]
		else:
			self.raptor.Warn("Empty list or blank path supplied.")

		# Only when debugging, print the list. The for loop will be
		# skipped if not in debug mode
		if self.raptor.debugOutput:
			for path in pathlist:
				self.raptor.Debug("Loading XML cache from %s", path)

		# Internal function to get the list of XML
		# files recursively
		def getXmlFiles(aDir, aFileList):
			dirList = []
			for fname in os.listdir(aDir):
				path = os.path.join(aDir, fname)
				if os.path.isdir(path):
					dirList.append(path)
				else: # It's a file
					if path.endswith(".xml"): # Only files ending in .xml get added
						aFileList.append(path)
			# Now iterate over directory list; this way, the files in the top level of
			# aDir will be added before all files in any subdirectory of aDir
			for dir in dirList:
				getXmlFiles(dir, aFileList)

		# This will add all files in all top level directories and all XML files
		for path in pathlist:
			# gPathOrGPathList passed to Load() can be a file or a
			# directory, or a list of files or directories or both
			if os.path.isfile(path):
				if path.endswith(".xml"): # Only files whose names end in .xml get added
					filenames.append(path)
			elif os.path.isdir(path):
				getXmlFiles(path, filenames)
			else: # it isn't a file or directory
				self.raptor.Warn("No file or directory found for '%s'", path)

		if not filenames:
			# No XML files found in any of the paths
			return

		# Parse XML files, and add the objects to our
		# configuration/interface/variant dictionaries
		for fullpath in filenames:
			try:
				objects = raptor_xml.Read(self.raptor, fullpath)

			except raptor_xml.XMLError:
				self.raptor.Warn("Failed to read XML file %s", fullpath)
				continue

			self.raptor.Debug("%d objects found in XML file %s", len(objects), fullpath)

			for obj in objects:
				# top-level objects need to know which XML file they came from.
				obj.SetSourceFile(fullpath)
				try:
					self.AddObject(obj, cacheID)
				except UnexpectedObjectError:
					self.raptor.Warn("Unexpected object %s", str(obj))

	def AddObject(self, obj, cacheID):
		"""add a Group, Alias, Interface or Variant.

		The cacheID is only used to separate Interfaces.
		"""

		if isinstance(obj, raptor_data.Group):
			self.AddGroup(obj)
		elif isinstance(obj, raptor_data.Alias):
			self.AddAlias(obj)
		elif isinstance(obj, raptor_data.Interface):
			self.AddInterface(obj, cacheID)
		elif isinstance(obj, raptor_data.Variant):
			self.AddVariant(obj)
		else:
			raise UnexpectedObjectError


	def FindNamedGroup(self, name):
		return self.groups[name]

	def AddGroup(self, obj):
		if obj.name in self.groups:
			self.WarnDuplicate("group", self.groups[obj.name], obj)
			return

		self.groups[obj.name] = obj

	def FindNamedAlias(self, name):
		return self.aliases[name]

	def AddAlias(self, obj):
		if obj.name in self.aliases:
			self.WarnDuplicate("alias", self.aliases[obj.name], obj)
			return

		self.aliases[obj.name] = obj


	def FindNamedInterface(self, name, cacheID = ""):
		try:
			return self.interfaces[cacheID][name]
		except KeyError, e:
			if cacheID == "":
				raise e
			else:
				return self.interfaces[""][name]


	def AddInterface(self, obj, cacheID):
		if not cacheID in self.interfaces:
			self.interfaces[cacheID] = {}

		if obj.name in self.interfaces[cacheID]:
			self.WarnDuplicate("interface", self.interfaces[cacheID][obj.name], obj)
			return

		obj.cacheID = cacheID
		self.interfaces[cacheID][obj.name] = obj


	def FindNamedVariant(self, name):
		return self.variants[name]


	def AddVariant(self, obj):
		# anonymous variants can never be referenced, so ignore them
		if obj.name:
			if self.variants.has_key(obj.name):
				self.WarnDuplicate("variant", self.variants[obj.name], obj)
				return

			self.variants[obj.name] = obj


	def WarnDuplicate(self, type, objOld, objNew):
		"""tell us where duplicate objects came from."""
		oldSource = objOld.source
		if oldSource == None:
			oldSource = "unknown"

		newSource = objNew.source
		if newSource == None:
			newSource = "unknown"

		# don't warn if we are reloading the object from the same
		# file as before: since that is quite ligitimate.
		if oldSource == newSource and oldSource != "unknown":
			return

		# actually this is just for information not a warning
		self.raptor.Info("Duplicate %s '%s' (the one from '%s' will override the one in '%s')",
						 type, objOld.name, oldSource, newSource)


class UnexpectedObjectError(Exception):
	pass


# end of the raptor_cache module
